Greasemonkey-scripts and Chrome

Discuss developing tools and get details on Conquer Club's API.

Moderators: Tech Team, Global Moderators

Forum rules
Please read the Community Guidelines before posting.

Greasemonkey-scripts and Chrome

Postby sherkaner on Sun Dec 19, 2010 7:35 am

Time for me to share some of my knowledge of Greasemonkey scripts in Chrome.

Basically, if you have a script that works in firefox, the chance is quite large that you'll be able to convert it to a chrome script.
There are a few things that just won't work. Most notably:
  • @require (to add a static file along with your script) doesn't work. See http://www.chromium.org/developers/desi ... er-scripts for the latest support.
  • Unsafewindow gives access to the javascript of the page. There is no way to get access to that through chrome, only HTML-elements and firing events on those elements.
  • There is no way to do cross-side requests (normally). Greasemonkey allows this usually, because every request is seen as fetching a new page, and not as 1 page requesting another page.

The basics are just to remove every use of the previous 3 points. The 2nd point was the hardest for me, but it is considered to be bad practice even in Greasemonkey itself, so it had to be done anyway.

But then, there are still some methods in greasemonkey that might not be implemented in chrome, most noticably the getValue and setValue-methods.

The best way to solve this is to add those functions and do something similar if they don't exist already. This is the piece of script I use at the moment:

Code: Select all
if((typeof GM_getValue == 'undefined') || (GM_getValue('a', 'b') == undefined)) {
   var namespace = "BOB.";
   GM_addStyle = function(css) {
      var style = document.createElement('style');
      style.textContent = css;
      document.getElementsByTagName('head')[0].appendChild(style);
   }

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

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

   GM_listValues = function() {
      var i, name ,result = [];
      for (i = 0; i < localStorage.length; i++) {
         name = localStorage.key(i);
         if (name.indexOf(namespace) == 0) {
            result.push(name.slice(namespace.length));
         }
      }
      return result;
   }
   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 request;
      }
      if(obj.headers) {
         for(var name in obj.headers) {
            request.setRequestHeader(name,obj.headers[name]);
         }
      }
      request.send(obj.data);
      return request;
   }

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

(Most of this code was found in a userscripts-thread btw, I made some small adjustments)

For storage, it uses the localStorage that used to be part of HTML5, and is implemented in the newer browsers. There is one caveat for that: those values get set per website, so http://www.conquerclub.com has 1 localStorage, and this could cause name collisions if multiple scripts try to save the same name. So the variable namespace defines a namespace now that should be different for every script, which should make it less likely to have naming collisions.

Most difficult thing at the moment is the mimicing of the GM_xmlhttpRequest. I don't know if it is possible to get around not being able to get a site, but for now I only need it for checking whether a new version is available, and I can live with that not working for chrome at the moment.
Colonel sherkaner
 
Posts: 1606
Joined: Thu May 03, 2007 3:21 am
Location: Zwolle
Medals: 42
Standard Achievement (3) Doubles Achievement (2) Triples Achievement (2) Quadruples Achievement (2) Terminator Achievement (2)
Freestyle Achievement (2) Nuclear Spoils Achievement (1) Fog of War Achievement (2) Speed Achievement (1) Teammate Achievement (2)
Random Map Achievement (1) Cross-Map Achievement (3) Ratings Achievement (2) Tournament Achievement (3) Clan Achievement (3)
General Contribution (11)

Re: Greasemonkey-scripts and Chrome

Postby sherkaner on Mon Dec 27, 2010 7:11 pm

Ow, what I forgot to mention:

Chrome has a good javascript-debugger, and you can actually debug extensions with it. So finding any remaining errors isn't as hard as it is with firefox/greasemonkey.
Colonel sherkaner
 
Posts: 1606
Joined: Thu May 03, 2007 3:21 am
Location: Zwolle
Medals: 42
Standard Achievement (3) Doubles Achievement (2) Triples Achievement (2) Quadruples Achievement (2) Terminator Achievement (2)
Freestyle Achievement (2) Nuclear Spoils Achievement (1) Fog of War Achievement (2) Speed Achievement (1) Teammate Achievement (2)
Random Map Achievement (1) Cross-Map Achievement (3) Ratings Achievement (2) Tournament Achievement (3) Clan Achievement (3)
General Contribution (11)

Re: Greasemonkey-scripts and Chrome

Postby dwilhelmi on Mon Dec 27, 2010 9:35 pm

sherkaner wrote:Most difficult thing at the moment is the mimicing of the GM_xmlhttpRequest. I don't know if it is possible to get around not being able to get a site, but for now I only need it for checking whether a new version is available, and I can live with that not working for chrome at the moment.


I actually came up with a way of doing version checking with Chrome that uses GM_xmlhttpRequest against CC, so it is allowed. Basically, instead of querying userscripts meta data for the version number, I just make sure that the forum post about the script contains the string "version x.x.x" in the subject. I then do a request against the forum page, and parse out a regex match for the version number.

The following code can be directly lifted into any script to perform version checking. To give due credit, this code was pretty much lifted directly out of BOB, with some minor changes to generalize some of the names. You must do 3 things in order to use it:
1. Somewhere above where you paste this code, define a function with no parameters called versionCheckComplete
2. In your initialization code, call checkForUpdate, passing the forum url as a parameter
3. Inside of your versionCheckComplete method, call isNewVersion to see if you need to display a notification

Code: Select all
function checkForUpdate(versionUrl) {
    var lastversion = GM_getValue('lastupdate', 0);
    if (lastversion < new Date().getTime() - 60*60*1000) {
        GM_setValue('lastupdate', new Date().getTime() + "");
        GM_xmlhttpRequest({
            method: 'GET',
            url: versionUrl,
            onload: updateServerNumber
        });
    }
    else {
        versionCheckComplete();
    }
}

function updateServerNumber(response) {
    try {
     var serverVersion = /version\s+(\d+.\d+.\d+)/.exec(response.responseText)[1];
     GM_setValue('updateavailable', serverVersion);
     versionCheckComplete();
    }catch(e){}
}

function isNewVersion() {
    var serverVersion = GM_getValue('updateavailable', false);
    if (serverVersion) {
        var newVersion = serverVersion.split('.').map(function(string) {
                return parseInt(string,10);
         });
         var thisVersion = version.split('.').map(function(string) {
                return parseInt(string,10);
         });
         return (newVersion[0]>thisVersion[0] || (newVersion[0]==thisVersion[0] && (newVersion[1]>thisVersion[1] || (newVersion[1]==thisVersion[1] && newVersion[2]>thisVersion[2]))));
    }
    return false;
}
User avatar
Brigadier dwilhelmi
 
Posts: 161
Joined: Tue Apr 06, 2010 10:05 am
Medals: 37
Standard Achievement (2) Doubles Achievement (2) Triples Achievement (2) Quadruples Achievement (3) Terminator Achievement (1)
Assassin Achievement (1) Manual Troops Achievement (1) Nuclear Spoils Achievement (1) Fog of War Achievement (3) Trench Warfare Achievement (1)
Teammate Achievement (1) Random Map Achievement (1) Cross-Map Achievement (3) Ratings Achievement (2) General Achievement (3)
Clan Achievement (9) General Contribution (1)


Return to Tools Development

Who is online

Users browsing this forum: No registered users

Login