Script Compatibility Instructions

March 28, 2010
Filed under: Uncategorized — kwah @ 5:10 pm

I’ve decided to not *actively* support browsers that do not run Greasemonkey or are not fully Greasemonkey-compatible.

More specifically, I’m pretty anti-Google Chrome regarding the privacy concerns (did you know that until a recent update a unique identifier was sent to Google with details about every URL that you visit? *) and Opera is a little bit awkward to code for due to lack of support for any form of storage available to the script (except for v11.5 which was recently released I think, which has localstorage available to scripts).

* after the backlash it received, it is now sent only when you install and / or update chrome

I realise that both of the issues I mentioned are both semi-irrelevant due to recent semi-solutions becoming available, but they are not the complete reasons for not supporting these browsers.

The Actual Reasons…

As some of you may know, I’m not going to be around for much longer (I leave to go travelling abroad for a couple of months on the 3rd April) and won’t be able to spend time coding or even at a computer much for that matter.

I do know that it would be unfair to just abandon thing, however, so I’m trying to leave behind some form of helpful instructions so that other people can pick up where I left off – this post is the first of these posts.

Instructions

The instructions to use the code are pretty simple if you’re trying to make a script cross-browser compatible, paste the above code into your script before you use any of the GM_ functions. Typically you can just paste it beneath the // ==/UserScript== line in the Greasemonkey script.

If you are not sure what this means, I suggest that you read more information about Greasmonkey scripts / javascript (or get someone to help you with it =] ).

The Code

The code is pretty heavily commented so hopefully there shouldn’t be any problems with understanding what it does, but if you have any problems that is what the blog comments are for ;)

// Browser Detection code
// http://www.javascripter.net/faq/browsern.htm
var nVer = navigator.appVersion;
var nAgt = navigator.userAgent;
var browserName  = navigator.appName;
var fullVersion  = ''+parseFloat(navigator.appVersion);
var majorVersion = parseInt(navigator.appVersion,10);
var nameOffset,verOffset,ix;

// In MSIE, the true version is after "MSIE" in userAgent
if ((verOffset=nAgt.indexOf("MSIE"))!=-1) {
  browserName = "Microsoft Internet Explorer";
  fullVersion = nAgt.substring(verOffset+5);
}
// In Opera, the true version is after "Opera"
else if ((verOffset=nAgt.indexOf("Opera"))!=-1) {
  browserName = "Opera";
  fullVersion = nAgt.substring(verOffset+6);
}
// In Chrome, the true version is after "Chrome"
else if ((verOffset=nAgt.indexOf("Chrome"))!=-1) {
  browserName = "Chrome";
  fullVersion = nAgt.substring(verOffset+7);
}
// In Safari, the true version is after "Safari"
else if ((verOffset=nAgt.indexOf("Safari"))!=-1) {
  browserName = "Safari";
  fullVersion = nAgt.substring(verOffset+7);
}
// In Firefox, the true version is after "Firefox"
else if ((verOffset=nAgt.indexOf("Firefox"))!=-1) {
 browserName = "Firefox";
 fullVersion = nAgt.substring(verOffset+8);
}
// In most other browsers, "name/version" is at the end of userAgent
else if ( (nameOffset=nAgt.lastIndexOf(' ')+1) < (verOffset=nAgt.lastIndexOf('/')) )
{
  browserName = nAgt.substring(nameOffset,verOffset);
  fullVersion = nAgt.substring(verOffset+1);
  if (browserName.toLowerCase()==browserName.toUpperCase()) {
      browserName = navigator.appName;
  }
}
// trim the fullVersion string at semicolon/space if present
if ((ix=fullVersion.indexOf(";"))!=-1) { fullVersion=fullVersion.substring(0,ix); }
if ((ix=fullVersion.indexOf(" "))!=-1) { fullVersion=fullVersion.substring(0,ix); }

majorVersion = parseInt(''+fullVersion,10);
if (isNaN(majorVersion)) {
  fullVersion  = ''+parseFloat(navigator.appVersion);
  majorVersion = parseInt(navigator.appVersion,10);
}

// Check if the Greasemonkey API functions are present
// If not, add/insert them

//// Chrome Compatibility Notes::
/*
// According to this source code: http://src.chromium.org/viewvc/chrome/trunk/src/chrome/renderer/resources/greasemonkey_api.js
// These API functions are supported: GM_addStyle, GM_xmlhttpRequest, GM_openInTab, GM_log
// These API functions are explicitly unsupported: GM_getValue, GM_setValue, GM_registerMenuCommand
// NOTE: GM_xmlhttpRequest appears to be subject to same-origin policies (unless permissions are explicitly requested via a manifest file); currently researching this impact on user scripts
// http://code.google.com/chrome/extensions/xhr.html
// Update 16-Feb:: It appears that userscripts are run as 'content scripts' thus do not have access to xhr (cross-domain) functionality - http://code.google.com/chrome/extensions/content_scripts.html
*/

function toBool(str) {
  if ("false" === str)     { return false; }
  else if ("true" === str) { return true; }
  else                     { return str; }
}

// GM_log()
if('undefined' == typeof GM_log) {
  if('undefined' !== typeof console) {
  // Greasemonkey-specific API for message logging (level: information)
    console.info('typeof console.info = '+(typeof console.info));
    function GM_log(message) {
      console.info(message);
    }
  }
  else if('undefined' !== typeof opera.postError) {
  // Opera-specific API for message logging (level: information)
    opera.postError('typeof opera.postError = '+(typeof opera.postError));
    function GM_log(message) {
      opera.postError(message);
    }
  }
  // else if('undefined' !== typeof Error) {
  // // Generic API for throwing errors (level: error)
  // // NOTE: throw() will halt the execution of the script
    // console.info('typeof Error = '+(typeof Error));
    // function GM_log(message) {
      // throw new Error(message);
    // }
  // }
}

//// Options for replacing GM_get/setValue ::
/*
// Cookie storage::
// Decision: Cannot use cookies as cookies are sent to the server on *EVERY* page request
// Problem1: Will potentially(?) make the site seem sluggish
// Problem2: Potential privacy concern: will enable Neobux to track your usage of scripts 

// HTML5 localStorage::
// Problem1: localStorage databases are scoped to an HTML5 origin, thus values stored when viewing http:// pages are not accessible via https:// pages (and vice versa)
// Problem2: Still a potential privacy problem as data stored by localStorage is also accessible by the page itself,  but as the data does not get sent to the server every time a page is loaded there is less overhead and the fact that you are using a script is not "broadcasted"

// Decision::
// Will try using HTML5 localStorage to implement similar functionality as GM_get/setValue (lesser of the evils?)
// Store item in local storage:
*/

// GM_setValue()
if('undefined' == typeof GM_setValue) {
  function GM_setValue(key,value) {
    try {
      window.localStorage[key] = value;
    } catch(e) {
      GM_log("Error inside GM_setValue");
      GM_log(e);
    }
    // GM_log("Return from GM_setValue.\n" + key + ":" +  value);
  }
}

// GM_getValue()
if('undefined' == typeof GM_getValue) {
  function GM_getValue(key,value) {
    // GM_log('Get Item:' + key);

    if('undefined' !== typeof window.localStorage) {
      if('undefined' !== window.localStorage[key] ) {
              // if ("false" === window.localStorage[key]) {
                // return false;
              // } else if ("true" === window.localStorage[key]) {
                // return true;
              // } else {
        return window.localStorage[key];
              // }
      }
      else if('undefined' !== typeof value)
      {
        GM_log("GM_getValue: Could not find key:" + key);
        GM_log("GM_getValue: Attempting to set key value to the supplied default. Default = "+value);
        GM_setValue(key,value);
        GM_log("GM_getValue: Returning default value. Default = "+value);
        return value;
      }
      else
      {
        GM_log("GM_getValue: Unable to retrieve value and no default set.");
        return undefined;
      }
    }
  }
}

// GM_registerMenuCommand()
if('undefined' == typeof GM_registerMenuCommand) {
  function GM_registerMenuCommand() { }
}

// GM_log()
if('undefined' == typeof GM_log) {
  if('undefined' !== typeof console) {
    console.info('typeof console.info = '+(typeof console.info));
    function GM_log(message) { console.info(message); }
  }
  else if('undefined' !== typeof window.opera  && 'undefined' !== typeof opera.postError)
  {
    function GM_log(message) { opera.postError(message); }
  }
  // else if('undefined' !== typeof Error) {
    // console.info('typeof Error = '+(typeof Error));
    // function GM_log(message) { throw new Error(message); }
  // }

}

// GM_addStyle()
// taken from actual greasemonkey source
if('undefined' == typeof GM_addStyle) {
  function GM_addStyle(css) {
    var head = document.getElementsByTagName("head")[0];
    if (head) {
      var style = document.createElement("style");
      style.textContent = css;
      style.type = "text/css";
      head.appendChild(style);
    }
    return style;
  }
}

Leave a Reply

You must be logged in to post a comment.