12

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:

Community
  • 1
  • 1
Chris Noe
  • 36,411
  • 22
  • 71
  • 92
  • 1
    there is another 10 question tagged as hidden-features. http://stackoverflow.com/questions/tagged/hidden-features – Jakub Šturc Sep 23 '08 at 14:10

10 Answers10

13

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
  • 10,027
  • 5
  • 46
  • 65
  • 1
    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 Sep 23 '08 at 15:20
13
==UserScript==
...
@require http://ajax.googleapis.com/ajax/framework-of-your/choice.js
==/UserScript==
mislav
  • 14,919
  • 8
  • 47
  • 63
10

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
  • 36,411
  • 22
  • 71
  • 92
  • 2
    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 Sep 24 '08 at 16:18
  • 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 Sep 26 '08 at 02:50
8

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
  • 36,411
  • 22
  • 71
  • 92
  • Is this really a hidden feature or even lesser-known? It's part of Greasemonkey's documented API, which is itself very small. – Jonny Buchanan Sep 23 '08 at 14:15
  • Agreed that this is not hidden per se. But it falls into the idiom category. – Chris Noe Sep 23 '08 at 14:28
  • 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 Sep 23 '08 at 15:28
5

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. If you're dealing with pure JSON values (rather than JavaScript objects), use JSON.stringify to store and JSON.parse to retrieve; this will be both faster and safer.

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.

PotatoEngineer
  • 1,572
  • 3
  • 20
  • 26
  • 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 Mar 20 '09 at 12:25
  • I've learn't something today. Was looking at using JSON to persist an array, but this looks pretty sweet. – davewasthere Sep 26 '09 at 10:49
3

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
  • 36,411
  • 22
  • 71
  • 92
  • 3
    ... and while you're at it, you can also send their usernames and passwords with it – Alister Bulman Sep 23 '08 at 14:36
  • 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 Sep 23 '08 at 15:31
2

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
  • 36,411
  • 22
  • 71
  • 92
2

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
  • 12,344
  • 10
  • 72
  • 106
  • 1
    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/listings http://www.meevee.com/myguide.aspx – Chris Noe Sep 26 '08 at 02:48
  • I've had a few issues creating Greasemonkey scripts for SO due to issues like this. :-) – Ben Blank Mar 19 '09 at 23:55
1

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?

Community
  • 1
  • 1
Chris Noe
  • 36,411
  • 22
  • 71
  • 92
1

Obsolete: Firefox dropped support for E4X, in Greasemonkey scripts, with FF version 17. Use GM_info to get metadata.


You can use e4x to access your ==UserScript== information as a variable:

var metadata=<> 
// ==UserScript==
// @name           search greasemonkey
// @namespace      foo
// @include        http://*.google.com/*
// @include        http://*.google.ca/*
// @include        http://search.*.com/*
// @include        http://*.yahoo.com/*
// ==/UserScript==
</>.toString();
Brock Adams
  • 90,639
  • 22
  • 233
  • 295
Darth Egregious
  • 18,184
  • 3
  • 32
  • 54
  • 1
    +1 but this is obsolete now that [`GM_info` was added to Greasemonkey](http://stackoverflow.com/a/10475344/331508). – Brock Adams May 07 '12 at 00:10