// userScripts.uc.js:
//   customize webpages with javascript ala Greasemonkey
  
var userScripts = {
  prefs: Components.classes['@mozilla.org/preferences-service;1']
                   .getService(Components.interfaces.nsIPrefService),
  enabledPref: 'userscripts.enabled',
  enabled: true,
  lastModifiedTime: 0,
  scripts: [],
  observe: function(subject, topic, data)
  {
    if (topic != 'nsPref:changed')
      return;
  
    switch (data) {
      case this.enabledPref:
        try {
          this.enabled = this.prefs.getBoolPref(this.enabledPref);
        } catch (e) { }
        break;
      default:
        break;
    }
  },
  stringToRegExp: function(string)  // thanks adblock/greasemonkey
  {
    var s = new String(string);
    var str = '';
  
    for (var i = 0; i < s.length; i++) {
      switch (s[i]) {
        case '*':
          str += ".*";
          break;
        case '.':
        case '?':
        case '^':
        case '$':
        case '+':
        case '{':
        case '[':
        case '|':
        case '(':
        case ')':
        case ']':
          str += "\\" + s[i];
          break;
        case '\\':
          str += "\\\\";
          break;
        case ' ':
          break;
        default:
          str += s[i];
          break;
      }
    }
  
    if (str)
      return new RegExp('^' + str + '$', 'i');
  },
  getScriptContent: function(file)
  {
    var content = '';
  
    var stream = Components.classes['@mozilla.org/network/file-input-stream;1']
                      .createInstance(Components.interfaces.nsIFileInputStream);
    stream.init(file, 0x01, 0, 0);
  
    var script = Components.classes['@mozilla.org/scriptableinputstream;1']
                .createInstance(Components.interfaces.nsIScriptableInputStream);
    script.init(stream);
  
    if (stream.available()) {
      var data = script.read(4096);
  
      while (data.length > 0) {
        content += data;
        data = script.read(4096);
      }
    }
  
    stream.close();
    script.close();
  
    return content.length ? content : null;
  },
  getScriptData: function(file)
  {
    var content = this.getScriptContent(file);
  
    if (!content)
      return null;
  
    var header = null;
  
    var start = content.indexOf('==UserScript==');
  
    if (start > 0) {
      var end = content.indexOf('==/UserScript==');
  
      if (end > 0)
        header = content.substr(start, end);
    }
  
    if (!header)
      return null;
  
    var includes = [],
        excludes = [];
  
    var lines = header.split(/[\r\n]+/);
  
    for (var i = 0; i < lines.length; i++) {
      var match = lines[i].match(/@(r?include|r?exclude)+\s+([^\n]+)/i);
  
      if (match) {
        var include = null,
            exclude = null;
  
        switch (match[1]) {
          case 'include':
            include = this.stringToRegExp(match[2]);
            break;
          case 'rinclude':
            try { include = new RegExp(match[2], 'i'); } catch (e) { }
            break;
          case 'exclude':
            exclude = this.stringToRegExp(match[2]);
            break;
          case 'rexclude':
            try { exclude = new RegExp(match[2], 'i'); } catch (e) { }
            break;
          default:
            break;
        }
  
        if (include)
          includes.push(include);
        else if (exclude)
          excludes.push(exclude);
      }
    }
  
    if (!includes.length)
      return null;
  
    return { lastModifiedTime: file.lastModifiedTime, file: file,
             includes: includes, excludes: excludes, content: content };
  },
  updateScripts: function()
  {
    if (!this.home.exists()) {
      this.lastModifiedTime = 0;
      this.scripts = [];
      return;
    }
  
    if (this.lastModifiedTime == this.home.lastModifiedTime)
      return;
  
    this.lastModifiedTime = this.home.lastModifiedTime;
  
    var scripts = [];
  
    // check for deleted user scripts
    for (var name in this.scripts)
      if (this.scripts[name].file.exists())
        scripts[name] = this.scripts[name];
  
    var files = this.home.directoryEntries
                    .QueryInterface(Components.interfaces.nsISimpleEnumerator);
  
    while (files.hasMoreElements()) {
      var file = files.getNext()
                      .QueryInterface(Components.interfaces.nsIFile);
      var name = file.leafName;
  
      if (/\.user\.js$/i.test(name)) {
        var s = scripts[name];
  
        if (!s || s.lastModifiedTime != file.lastModifiedTime) {
          var data = this.getScriptData(file);
  
          if (data)
            scripts[name] = data;
        }
      }
    }
  
    this.scripts = scripts;
  },
  scriptMatch: function(script, href)
  {
    var match = false;
  
    for (var i = 0; i < script.includes.length; i++)
      if (script.includes[i].test(href)) {
        match = true;
        break;
      }
  
    if (match)
      for (var i = 0; i < script.excludes.length; i++)
        if (script.excludes[i].test(href)) {
          match = false;
          break;
        }
  
    return match;
  },
  init: function()
  {
    this.home = Components.classes["@mozilla.org/file/directory_service;1"]
                          .getService(Components.interfaces.nsIProperties)
                          .get('ProfD', Components.interfaces.nsILocalFile);
    this.home.append('userscripts');

    if (!this.home.exists())
      this.home.create(this.home.DIRECTORY_TYPE, 0755);
  
    try {
      this.enabled = this.prefs.getBoolPref(this.enabledPref);
    } catch (e) { }
  
    if (this.enabled)
      this.updateScripts();
  
    var pbi = Components.classes["@mozilla.org/preferences-service;1"]
                        .getService(Components.interfaces.nsIPrefBranch2);
    pbi.addObserver(this.enabledPref, this, false);
  
    var appcontent = window.document.getElementById("appcontent");
  
    if (appcontent)
      appcontent.addEventListener("DOMContentLoaded",
        function(e) {
          if (!userScripts.enabled)
            return;
  
        //var window   = e.target.defaultView.wrappedJSObject;
          var document = e.target;
  
          if (!document.location)
            return;
  
          var href = document.location.href;
  
          if (!/^(https?|file):/i.test(href))
            return;
  
          var head = document.getElementsByTagName('head')[0];
  
          if (head) {
            userScripts.updateScripts();
  
            for (var name in userScripts.scripts) {
              var s = userScripts.scripts[name];
  
              if (userScripts.scriptMatch(s, href)) {
                var e = document.createElement('script');
                e.innerHTML = '(function() {\n' + s.content  + '})();';
                head.appendChild(e);
              }
            }
          }
        }, true);
  }
};
  
userScripts.init();
