Greasemonkey API emulation for Chrome

in
Subscribe to Greasemonkey API emulation for Chrome 23 posts, 10 voices



aeosynth User
FirefoxX11

I just updated a couple of my userscripts to work in Chrome using this snippet:

// @copyright      2009, 2010 James Campos
// @license        cc-by-3.0; http://creativecommons.org/licenses/by/3.0/
if (typeof GM_deleteValue == 'undefined') {

    GM_addStyle = function(css) {
        var style = document.createElement('style');
        style.textContent = css;
        document.getElementsByTagName('head')[0].appendChild(style);
    }

    GM_deleteValue = function(name) {
        localStorage.removeItem(name);
    }

    GM_getValue = function(name, defaultValue) {
        var value = localStorage.getItem(name);
        if (!value)
            return defaultValue;
        var type = value[0];
        value = value.substring(1);
        switch (type) {
            case 'b':
                return value == 'true';
            case 'n':
                return Number(value);
            default:
                return value;
        }
    }

    GM_log = function(message) {
        console.log(message);
    }

    GM_openInTab = function(url) {
        return window.open(url, "_blank");
    }

     GM_registerMenuCommand = function(name, funk) {
    //todo
    }

    GM_setValue = function(name, value) {
        value = (typeof value)[0] + value;
        localStorage.setItem(name, value);
    }
}

edit: changed get/set value wrappers
edit: added copyright info
edit: changed function x to x = function since Opera would overwrite the functions provided by the Emulate GM functions script otherwise.
edit: test for GM_deleteValue instead of GM_getValue, since chrome's half-assed implementation throws it off.
Nov 14 2010: added GM_openInTab emulator

edit 2011 Aug 27:

I currently get/set using JSON:

GM_getValue = function(name, defaultValue) {
  var value = localStorage[name];
  return value == null ? defaultValue : JSON.parse(value);
}

GM_setValue = function(name, value) {
  localStorage[name] = JSON.stringify(value);
}

 
GIJoe Scriptwright
SeamonkeyMacintosh

I use this:

// @author        GIJoe
// @license       http://creativecommons.org/licenses/by-nc-sa/3.0/

//--- to test localStorage in firefox
//delete GM_log; delete GM_getValue; delete GM_setValue; delete GM_deleteValue; delete GM_xmlhttpRequest; delete GM_openInTab; delete GM_registerMenuCommand;

var gvar=function() {} // Global variables
function GM_ApiBrowserCheck() {
  const GMSTORAGE_PATH = 'GM_'; // You can change it to avoid conflict with others scripts
  if(typeof(unsafeWindow)=='undefined') { unsafeWindow=window; }
  if(typeof(GM_log)=='undefined') { GM_log=function(msg) { try { unsafeWindow.console.log('GM_log: '+msg); } catch(e) {} }; }
  GM_clog=function(msg) { if(arguments.callee.counter) { arguments.callee.counter++; } else { arguments.callee.counter=1; } GM_log('('+arguments.callee.counter+') '+msg); }
  GM_addGlobalStyle=function(css) { // Redefine GM_addGlobalStyle with a better routine
    var sel=document.createElement('style'); sel.setAttribute('type','text/css'); sel.appendChild(document.createTextNode(css));
    var hel=document.documentElement.firstChild; while(hel && hel.nodeName!='HEAD') { hel=hel.nextSibling; }
    if(hel && hel.nodeName=='HEAD') { hel.appendChild(sel); } else { document.body.insertBefore(sel,document.body.firstChild); }
    return sel;
  }
  var needApiUpgrade=false;
  if(window.navigator.appName.match(/^opera/i) && typeof(window.opera)!='undefined') {
    needApiUpgrade=true; gvar.isOpera=true; GM_log=window.opera.postError; GM_log('Opera detected...');
  }
  if(typeof(GM_setValue)!='undefined') {
    var gsv=GM_setValue.toString();
    if(gsv.indexOf('staticArgs')>0) { gvar.isGreaseMonkey=true; GM_log('GreaseMonkey Api detected...'); } // test GM_hitch
    else if(gsv.match(/not\s+supported/)) { needApiUpgrade=true; gvar.isBuggedChrome=true; GM_log('Bugged Chrome GM Api detected...'); }
  } else { needApiUpgrade=true; GM_log('No GM Api detected...'); }

  if(needApiUpgrade) {
    GM_log('Try to recreate needed GM Api...');
    var ws=null; try { ws=typeof(unsafeWindow.localStorage); unsafeWindow.localStorage.length; } catch(e) { ws=null; } // Catch Security error
    if(ws=='object') {
      GM_log('Using localStorage for GM Api.');
      GM_getValue=function(name,defValue) { var value=unsafeWindow.localStorage.getItem(GMSTORAGE_PATH+name); if(value==null) { return defValue; } else { switch(value.substr(0,2)) { case 'S]': return value.substr(2); case 'N]': return parseInt(value.substr(2)); case 'B]': return value.substr(2)=='true'; } } return value; }
      GM_setValue=function(name,value) { switch (typeof(value)) { case 'string': unsafeWindow.localStorage.setItem(GMSTORAGE_PATH+name,'S]'+value); break; case 'number': if(value.toString().indexOf('.')<0) { unsafeWindow.localStorage.setItem(GMSTORAGE_PATH+name,'N]'+value); } break; case 'boolean': unsafeWindow.localStorage.setItem(GMSTORAGE_PATH+name,'B]'+value); break; } }
      GM_deleteValue=function(name) { unsafeWindow.localStorage.removeItem(GMSTORAGE_PATH+name); }
    } else if(!gvar.isOpera || typeof(GM_setValue)=='undefined') {
      GM_log('Using temporarilyStorage for GM Api.'); gvar.temporarilyStorage=new Array();
      GM_getValue=function(name,defValue) { if(typeof(gvar.temporarilyStorage[GMSTORAGE_PATH+name])=='undefined') { return defValue; } else { return gvar.temporarilyStorage[GMSTORAGE_PATH+name]; } }
      GM_setValue=function(name,value) { switch (typeof(value)) { case "string": case "boolean": case "number": gvar.temporarilyStorage[GMSTORAGE_PATH+name]=value; } }
      GM_deleteValue=function(name) { delete gvar.temporarilyStorage[GMSTORAGE_PATH+name]; };
    }
    if(typeof(GM_openInTab)=='undefined') { GM_openInTab=function(url) { unsafeWindow.open(url,""); } }
    if(typeof(GM_registerMenuCommand)=='undefined') { GM_registerMenuCommand=function(name,cmd) { GM_log("Notice: GM_registerMenuCommand is not supported."); } } // Dummy
    if(!gvar.isOpera || typeof(GM_xmlhttpRequest)=='undefined') {
      GM_log('Using XMLHttpRequest for GM Api.');
      GM_xmlhttpRequest=function(obj) {
        var request=new XMLHttpRequest();
        request.onreadystatechange=function() { if(obj.onreadystatechange) { obj.onreadystatechange(request); }; if(request.readyState==4 && obj.onload) { obj.onload(request); } }
        request.onerror=function() { if(obj.onerror) { obj.onerror(request); } }
        try { request.open(obj.method,obj.url,true); } catch(e) { if(obj.onerror) { obj.onerror( {readyState:4,responseHeaders:'',responseText:'',responseXML:'',status:403,statusText:'Forbidden'} ); }; return; }
        if(obj.headers) { for(name in obj.headers) { request.setRequestHeader(name,obj.headers[name]); } }
        request.send(obj.data); return request;
  } } }
}
GM_ApiBrowserCheck();

//--- to test some value defined by GM_ApiBrowserCheck
//GM_clog('isOpera='+gvar.isOpera);
//GM_clog('isGreaseMonkey='+gvar.isGreaseMonkey);
//GM_clog('isBuggedChrome='+gvar.isBuggedChrome);

 
Yansky Scriptwright
FirefoxWindows

I don't think you need to use unsafeWindow with localStorage. It seems to be available to scripts.

 
GIJoe Scriptwright
SeamonkeyMacintosh

As far as i remember, i had some problems if i didn't use unsafeWindow in Firefox.

Even in my Storage Viewer, i use unsafeWindow.

 
sizzlemctwizzle Scriptwright
FirefoxMacintosh

I personally prefer the simplicity of aeosynth's solution over GIJoe's. However, aeosynth does need to implement GM_xmlhttprequest.

 
aeosynth User
FirefoxX11

GIJoe's implementation of GM_xmlhttprequest looks solid; I would change GM_xmlhttpRequest=function(obj) to function GM_xmlhttpRequest(obj) and format it differently, but those are just my preferences.

 
Brian Hartvi... Scriptwright
ChromeWindows

These methods do not appear to work on the latest developer release (and likely recent betas.) They've put stub functions in place that do nothing more than say "undefined is not supported." GM_addStyle, GM_xmlhttpRequest, GM_openInTab, & GM_log are implemented. There are previous revisions where GM_(get|set|delete)Value worked as well, but those were yanked when they enabled userscript support for Linux and Mac (my guess is localStorage doesn't work correctly on them...) See http://src.chromium.org/viewvc/chrome/trunk/src... .

 
aeosynth User
FirefoxX11

I updated my code. Talking about this here and in the other thread is kind of awkward.

 
riddle Scriptwright
SafariMacintosh

I have to be the bad guy and remind of serious performance problems that occur when scripts save too much data via GM_setValue. All this data is saved to about:config – I remember one Last.fm script that cached everything there, resulting in 15MB size increase for that file (prefs.js).

That’s the reason I personally only use localStorage. Firefox 3.5, Safari 4 and Chrome 3 support it equally. Opera can get cookies if needed. Coupled with JSON, localStorage is a magnificent solution available here & now. HTML5 rocks. :)

 
sizzlemctwizzle Scriptwright
FirefoxMacintosh

riddle wrote:
That’s the reason I personally only use localStorage.

Unfortunately, only GM_setValue works cross-domain(would be nice if GM devs could get localStorage working cross-domain for scripts). If you don't need that then yes, localStorage is better. The latest version of Opera has localStorage support so no need to use cookies, unless you want to support old browsers.

 
GIJoe Scriptwright
SeamonkeyMacintosh

The default limit size of localStorage is 5MB in firefox.

 
sizzlemctwizzle Scriptwright
FirefoxMacintosh

GIJoe wrote:
The default limit size of localStorage is 5MB in firefox.

Per website or total?

 
riddle Scriptwright
SafariMacintosh

Thanks for clarification. I haven’t really needed cross-domain storage.

Regarding size limit – 5MB (per domain IIRC) is reasonable, it’s a lot of text. When one dumps a lot of data to prefs.js, Firefox slows down. A lot – all those prefs are read on startup.

 
GIJoe Scriptwright
SeamonkeyMacintosh

Per website or total?
Per domain.

 
Marti Scriptwright
FirefoxX11

GIJoe wrote:
The default limit size of localStorage is 5MB in firefox.
This is controlled via a quota management system and can be increased or decreased. The preference is dom.storage.default_quota.

Only bad thing about DOM Storage is that once it's enabled anyone can use it... I haven't searched for a management application yet for all storage values on every domain that is stored, but I do know that the Better Privacy extension usually disables DOM Storage which is preferred. When combined with Flash and JavaScript one can push around tracking data on a users activities on a specific domain. With cross domain allowances on the Flash object and the top level web site one can potentially scoot the data to another domain... this is all relatively new in the specs so I'm sure that DOM Storage has some holes in all browsers or different implementations... time will uncover them if they are present.

 
GIJoe Scriptwright
SeamonkeyMacintosh

DOM Storage don't work when cookies are disabled... (at least, in firefox)

 
Vy Ho Scriptwright
FirefoxX11

What's the security implication of local storage? Can the site read this information since it's in a same domain? This is not just localStorage in general, but rather in the extension space. So each maybe different than other.
So, how Chrome does it? How about Firefox? Opera?

 
sizzlemctwizzle Scriptwright
FirefoxMacintosh

Vy Ho wrote:
Can the site read this information since it's in a same domain?

Yes it can.

 
Piyush Soni Scriptwright
Firefox

Two questions.
1). Will the localstorage get cleared when we clear all our 'private data' or is it persistent like GM_setValue?
2). I read somewhere that the localStorage values set on an http://example.com will not be available on https://example.com . How do we fix that?

Thanks!

 
sizzlemctwizzle Scriptwright
FirefoxMacintosh

Piyush Soni wrote:
1). Will the localstorage get cleared when we clear all our 'private data' or is it persistent like GM_setValue?
Yes, localStorage will get cleared.
Piyush Soni wrote:
2). I read somewhere that the localStorage values set on an http://example.com will not be available on https://example.com . How do we fix that?
You can't. localStorage is attached to the domain it is defined on.

localStorage is a flawed storage mechanism for user scripts because it was designed for usage by a webpage, not a user script.

 
Piyush Soni Scriptwright
Firefox

sizzlemctwizzle wrote:
You can't. localStorage is attached to the domain it is defined on.

In that case, I think cookies are far better than localstorage for cloning GM_set/get operations. I know they are sent each time to the server so there's an overhead (and may make our script detectable - anyone care?), but I can at least access it from both http and https pages.

 
joeytwiddle Scriptwright
ChromeX11

I knocked up a cross-site GM_xmlhttpRequest, via a JSONP-proxy. The little proxy server is written for Node.

http://hwi.ath.cx/javascript/xhr_via_json/

It does open a little security hole!

You can also find in that script a three-liner to get a reference to unsafeWindow (thanks to Ventero in IRC #greasemonkey).

 
sizzlemctwizzle Scriptwright
FirefoxWindows

Piyush Soni wrote:
In that case, I think cookies are far better than localstorage for cloning GM_set/get operations.
How so? Cookies are tied to the domain as well, but at least with localStorage your values stay local.
joeytwiddle wrote:
The little proxy server is written for Node.
Unfortunately this is of a little use to anyone, since barely any web-hosts offer Node.js support. Would have been more beneficial if you wrote it in PHP (or even Perl). But thanks for sharing anyway.

Cross
Presentational HTML allowed.
Use <code> for inline code and <pre> for code blocks. Use &lt; and &gt; for literal < and >.
We help break paragraphs and link your links.
or cancel