views:

1781

answers:

9

What are some of the lesser-known but useful features and techniques that people are using in their Greasemonkey scripts?

(Please, just one feature per answer.)

Similar threads:

+6  A: 

Data can be persisted across page loads by storing it as a mozilla preference value via GM_setValue(keyname, value).

Here is a simple example that tallys the number of times your script has been executed - by a given browser:

var od = GM_getValue("odometer", 0);
od++;
GM_setValue("odometer", od);
GM_log("odometer=" + od);

GM values are analogous to cookies in that cookie values can only be accessed by the originated domain, GM values can only be accessed by the script that created them.

Chris Noe
Is this really a hidden feature or even lesser-known? It's part of Greasemonkey's documented API, which is itself very small.
insin
Agreed that this is not hidden per se. But it falls into the idiom category.
Chris Noe
Yeah, GM's API is really not that big and thus I don't know about 'hidden features' per se - would be more interesting to ask about interesting techniques/exploits that provide useful, non-obvious functionality.
Jason Bunting
+3  A: 

Anonymous statistics

Assuming you have a basic hosting service that provides access logging, you can easily track basic usage statistics for your script.

  1. Place a gif file (eg, a logo image) on your own website.
  2. In your script, attach an img element to the page that references the gif:
var img = document.createElement("img");
img.src = "http://mysite.com/logo.gif";
document.body.appendChild(img);

Now, each time a user executes your script, your hosting service will register a hit on that gif file.

To track more than one script, use a different gif file for each. Or add some kind of differentiating parameter to the URL, (eg: http://mysite.com/logo.gif?zippyver=1.0).

Chris Noe
... and while you're at it, you can also send their usernames and passwords with it</snark>
Alister Bulman
Please provide code example
Chris Noe
I actually had a script that would generate a pseudo-GUID when the script was installed, store that using GM_setValue, and then every *day* the script was run it would hit my website, sending the GUID in the query string. I logged this data to a database in order to get usage stats.
Jason Bunting
+8  A: 

Greasemonkey scripts often need to search for content on a page. Instead of digging through the DOM, try using XPath to locate nodes of interest. The document.evaluate() method lets you provide an XPath expression and will return a collection of matching nodes. Here's a nice tutorial to get you started. As an example, here's a script I wrote that causes links in phpBB3 posts to open in a new tab (in the default skin):

// ==UserScript==
// @name           New Tab in phpBB3
// @namespace      http://robert.walkertribe.com/
// @description    Makes links in posts in phpBB3 boards open new tabs.
// ==/UserScript==

var newWin = function(ev) {
    var win = window.open(ev.target.href);
    if (win) ev.preventDefault();
};

var links = document.evaluate(
        "//div[@class='content']//a[not(@onclick) and not(@href='#')]",
        document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

for (var i = 0; i < links.snapshotLength; i++) {
    var link = links.snapshotItem(i);
    link.addEventListener("click", newWin, true);
}

The XPath expression used in the code identifies all a elements that 1) do not have an onclick attribute, 2) whose href attribute is not set to "#", and 3) are found inside divs whose class attribute is set to "content".

Robert J. Walker
I would like to comment that despite widely expressed concerns that XPath might be too expensive as compared to DOM walking, I have found it to perform essentially instantaneously, even when used aggressively.
Chris Noe
+1  A: 

Script header values, (@name, @description, @version, etc), can be made retrievable. This is preferable to maintaining the same constant values in multiple places in your script.

See Accessing Greasemonkey metadata from within your script?

Chris Noe
+6  A: 

Your script can add graphics into a page, even if you don't have any place to host files, via data URIs.

For example, here is a little button graphic:

var button = document.createElement("img");
button.src = "data:image/gif;base64,"
    + "R0lGODlhEAAQAKEDAAAA/wAAAMzMzP///yH5BAEAAAMALAAAAAAQABAAAAIhnI+pywOtwINHTmpvy3rx"
    + "nnABlAUCKZkYoGItJZzUTCMFACH+H09wdGltaXplZCBieSBVbGVhZCBTbWFydFNhdmVyIQAAOw=="
somenode.appendChild(button);

Here is an online image encoder.

And a wikipedia article about the Data URI standard.

Chris Noe
If you do have a place to host files, but just don't have too much bandwidth, you can use the relatively recently-added @resource and GM_getResourceURL functions, so the user can download your image (or other resource) when they install your script. It's saved locally afterwards
Athena
Nice. That would be more performant as well, since the browser will cache the image, and doesn't have to construct it each time.
Chris Noe
+1  A: 

A useful XPath technique is to specify your match relative to a node that you have already found. As a contrived example for stackoverflow:

// first we got the username link at the top of the page
var hdrdiv = document.evaluate(
    "//div[@id='headerlinks']/a[1]", document, null,
    XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

// now we can retrieve text that follows it, (user's reputation score)
// (note that hdrdiv is now the contextNode argument, rather than document)
var reptext = document.evaluate(
    "following-sibling::span", hdrdiv, null,
    XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

alert("Reputation Score: " + reptext.textContent);

You can match in any direction relative to the contextNode, ancestors, descendants, previous, following. Here is a helpful XPath reference.

Chris Noe
+2  A: 

GreaseMonkey scripts run when the DOM is ready, so you don't need to add onload events, you just start manipulating the DOM straight away in your GreaseMonkey script.

Sam Hasler
The gotcha with some sites though is that they deliver incomplete HTML, and then modify it via onload. Therefore when you attempt to modify it via greasemonkey, the page isn't actually ready yet. For example:http://www.tvguide.com/listingshttp://www.meevee.com/myguide.aspx
Chris Noe
I've had a few issues creating Greasemonkey scripts for SO due to issues like this. :-)
Ben Blank
+12  A: 
==UserScript==
...
@require http://ajax.googleapis.com/ajax/framework-of-your/choice.js
==/UserScript==
mislav
+3  A: 

GM_setValue normally only stores 32-bit integers, strings, and booleans, but you can take advantage of the uneval() method (and a later eval() on retrieval) to store any object.

var foo={people:['Bob','George','Smith','Grognak the Destroyer'],pie:true};
GM_setValue('myVeryOwnFoo',uneval(foo));
var fooReborn=eval(GM_getValue('myVeryOwnFoo','new Object()'));
GM_log('People: '+fooReborn.people+'   Pie:'+fooReborn.pie);

I tend to use "new Object()" as my default in this case, but you could also use "({})". Just remember that "{}" evaluates as a string, not an object. As usual, eval() with care.

Paul Marshall
It makes for fairly unwieldly raw preference values, but very powerful. Somewhat like storing a BLOB in your database. I especially like your initialization tip.
Chris Noe
I've learn't something today. Was looking at using JSON to persist an array, but this looks pretty sweet.
davewasthere