Greasemonkey API emulation for Chrome
![]() ![]() |
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 2011 Aug 27: I currently get/set using 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);
}
|
![]() ![]() |
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);
|
![]() ![]() |
I don't think you need to use unsafeWindow with localStorage. It seems to be available to scripts. |
![]() ![]() |
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. |
![]() ![]() |
I personally prefer the simplicity of aeosynth's solution over GIJoe's. However, aeosynth does need to implement GM_xmlhttprequest. |
![]() ![]() |
GIJoe's implementation of GM_xmlhttprequest looks solid; I would |
![]() ![]() |
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... . |
![]() ![]() |
I updated my code. Talking about this here and in the other thread is kind of awkward. |
![]() ![]() |
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. :) |
![]() ![]() |
riddle wrote: 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. |
![]() ![]() |
The default limit size of localStorage is 5MB in firefox. |
![]() ![]() |
GIJoe wrote: Per website or total? |
![]() ![]() |
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. |
![]() ![]() |
Per website or total?Per domain. |
![]() ![]() |
GIJoe wrote: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. |
![]() ![]() |
DOM Storage don't work when cookies are disabled... (at least, in firefox) |
![]() ![]() |
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.
|
![]() ![]() |
Vy Ho wrote: Yes it can. |
![]() |
Two questions.
Thanks! |
![]() ![]() |
Piyush Soni wrote:Yes, localStorage will get cleared. Piyush Soni wrote: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. |
![]() |
sizzlemctwizzle wrote: 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. |
![]() ![]() |
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). |
![]() ![]() |
Piyush Soni wrote:How so? Cookies are tied to the domain as well, but at least with localStorage your values stay local. joeytwiddle wrote: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. |






