BUY THIS BOOK
Add to Cart

Print Book $24.95


Add to Cart

Print+PDF $32.44

Add to Cart

PDF $19.99

Safari Books Online

What is this?

Add to UK Cart

Print Book £17.50

What is this?

Looking to Reprint or License this content?


Greasemonkey Hacks
Greasemonkey Hacks Tips & Tools for Remixing the Web with Firefox By Mark Pilgrim
November 2005
Pages: 495

Cover | Table of Contents


Table of Contents

Chapter 1: Getting Started
The first thing you need to do to get started with Greasemonkey is install it. Open Firefox and go to http://greasemonkey.mozdev.org. Click the Install Greasemonkey link. Firefox will warn you that it prevented this site from installing software, as shown in Figure 1-1.
Figure 1-1: Firefox, requiring you to whitelist sites to install extensions
Click the Edit Options button to bring up the Allowed Sites dialog, as shown in Figure 1-2.
Figure 1-2: Allowed Sites dialog
Click the Allow button to add the Greasemonkey site to your list of allowed sites; then click OK to dismiss the dialog. Now, click the Install Greasemonkey link again, and Firefox will pop up the Software Installation dialog, as shown in Figure 1-3.
Figure 1-3: Software Installation dialog
Click Install Now to begin the installation process. After it downloads, quit Firefox and relaunch it to finish installing Greasemonkey.
Now that that's out of the way, let's get right to it.
Greasemonkey won't do anything until you start installing user scripts to customize specific web pages.
A Greasemonkey user script is a single file, written in JavaScript, that customizes one or more web pages. So, before Greasemonkey can start working for you, you need to install a user script.
Many user scripts are available at the Greasemonkey script repository: http://userscripts.org.
This hack shows three ways to install user scripts. The first user script I ever wrote was called Butler. It adds functionality to Google search results.
Here's how to install Butler from the context menu:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Hacks 1–12: Introduction
The first thing you need to do to get started with Greasemonkey is install it. Open Firefox and go to http://greasemonkey.mozdev.org. Click the Install Greasemonkey link. Firefox will warn you that it prevented this site from installing software, as shown in Figure 1-1.
Figure 1-1: Firefox, requiring you to whitelist sites to install extensions
Click the Edit Options button to bring up the Allowed Sites dialog, as shown in Figure 1-2.
Figure 1-2: Allowed Sites dialog
Click the Allow button to add the Greasemonkey site to your list of allowed sites; then click OK to dismiss the dialog. Now, click the Install Greasemonkey link again, and Firefox will pop up the Software Installation dialog, as shown in Figure 1-3.
Figure 1-3: Software Installation dialog
Click Install Now to begin the installation process. After it downloads, quit Firefox and relaunch it to finish installing Greasemonkey.
Now that that's out of the way, let's get right to it.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Install a User Script
Greasemonkey won't do anything until you start installing user scripts to customize specific web pages.
A Greasemonkey user script is a single file, written in JavaScript, that customizes one or more web pages. So, before Greasemonkey can start working for you, you need to install a user script.
Many user scripts are available at the Greasemonkey script repository: http://userscripts.org.
This hack shows three ways to install user scripts. The first user script I ever wrote was called Butler. It adds functionality to Google search results.
Here's how to install Butler from the context menu:
  1. Visit the Butler home page (http://diveintomark.org/projects/butler/) to see a brief description of the functionality that Butler offers.
  2. Right-click (Control-click on a Mac) the link titled "Download version…" (at the time of this writing, Version 0.3 is the latest release).
  3. From the context menu, select Install User Script….
  4. A dialog titled Install User Script will pop up, displaying the name of the script you are about to install (Butler, in this case), a brief description of what the script does, and a list of included and excluded pages. All of this information is taken from the script itself [Hack #2] .
  5. Click OK to install the user script.
If all went well, Greasemonkey will display the following alert: "Success! Refresh page to see changes."
Now, search for something in Google. In the search results page, there is a line at the top of the results that says "Try your search on: Yahoo, Ask Jeeves, AlltheWeb…" as shown in Figure 1-4. There is also a banner along the top that says "Enhanced by Butler." All of these options were added by the Butler user script.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Provide a Default Configuration
User scripts can be self-describing; they can contain information about what they do and where they should run by default.
Every user script has a section of metadata, which tells Greasemonkey about the script itself, where it came from, and when to run it. You can use this to provide users with information about your script, such as its name and a brief description of what the script does. You can also provide a default configuration for where the script should run: one page, one site, or a selection of multiple sites.
Save the following user script as helloworld.user.js:
	Example: Hello World metadata

	// ==UserScript==
	// @name		Hello World
	// @namespace	http://www.oreilly.com/catalog/greasemonkeyhcks/
	// @description	example script to alert "Hello world!" on every page
	// @include		*
	// @exclude		http://oreilly.com/*
	// @exclude		http://www.oreilly.com/*
	// ==/UserScript==

	alert('Hello world!');
There are five separate pieces of metadata here, wrapped in a set of Greasemonkey-specific comments.
Let's take them in order, starting with the wrapper:
	// ==UserScript==
	//
	// ==/UserScript==
These comments are significant and must match this pattern exactly. Greasemonkey uses them to signal the start and end of a user script's metadata section. This section can be defined anywhere in your script, but it's usually near the top.
Within the metadata section, the first item is the name:
	// @name	Hello World
This is the name of your user script. It is displayed in the install dialog when you first install the script and later in the Manage User Scripts dialog. It should be short and to the point.
@name is optional. If present, it can appear only once. If not present, it defaults to the filename of the user script, minus the .user.js extension.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Master the @include and @exclude Directives
Describing exactly where you want your user script to execute can be tricky.
As described in "Provide a Default Configuration" [Hack #2] , Greasemonkey executes a user script based on @include and @exclude parameters: URLs with * wildcards that match any number of characters. This might seem like a simple syntax, but combining wildcards to match exactly the set of pages you want is trickier than you think.
Here's a common scenario: a site is available at http://example.com and http://www.example.com. The site is the same in both cases, but neither URL redirects to the other. If you type example.com in the location bar, you get the site at http://example.com. If you visit www.example.com, you get exactly the same site, but the location bar reads http://www.example.com.
Let's say you want to write a user script that runs in both cases. Greasemonkey makes no assumptions about URLs that an end user might consider equivalent. If a site responds on both http://example.com and http://www.example.com, you need to declare both variations, as shown in this example:
	@include http://example.com/*
	@include http://www.example.com/*
Here's a slightly more complicated scenario. Slashdot is a popular technical news and discussion site. It has a home page, which is available at both http://slashdot.org and http://www.slashdot.org. But it also has specialized subdomains, such as http://apache.slashdot.org/, http://apple.slashdot.org/, and so forth.
Say you want to write a user script that runs on all these sites. You can use a wildcard within the URL itself to match all the subdomains, like this:
	@include http://slashdot.org/*
	@include http://*.slashdot.org/*
The first line matches when you visit http://slashdot.org. The second line matches when you visit http://www.slashdot.org (the * wildcard matches www). The second line also matches when you visit
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Prevent a User Script from Executing
You can disable a user script temporarily, disable all user scripts, or uninstall a user script permanently.
Once you have a few user scripts running, you might want to temporarily disable some or all of them. There are several different ways to prevent a user script from running.
The easiest way to disable a user script is in the Manage User Scripts dialog. Assuming you installed the Butler user script [Hack #1] , you can disable it with just a few clicks:
  1. From the menu bar, select Tools Manage User Scripts…. Greasemonkey will pop up the Manage User Scripts dialog.
  2. In the left pane of the dialog is a list of all the user scripts you have installed. (If you've been following along from the beginning of the book, this will include just one script: Butler.)
  3. Select Butler in the list if it is not already selected, and deselect the Enabled checkbox. The color of Butler in the left pane should change subtly from black to gray. (This is difficult to see while it is still selected, but it's more useful once you have dozens of scripts installed.)
  4. Click OK to exit the Manage User Scripts dialog.
Now, Butler is installed, but inactive. You can verify this by searching for something on Google. It should no longer say "Enhanced by Butler" along the top. You can reenable the Butler user script by repeating the procedure and reselecting the Enabled checkbox in the Manage User Scripts dialog.
Once disabled, a user script will remain disabled until you manually reenable it, even if you quit and relaunch Firefox.
While Greasemonkey is installed, it displays a little smiling monkey icon in the status bar, as shown in Figure 1-5.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Configure a User Script
There's more than one way to configure Greasemonkey user scripts: before, during, and after installation.
One of the most important pieces of information about a user script is where it should run. One page? Every page on one site? Multiple sites? All sites? This hack explains several different ways to configure where a user script executes.
As described in "Provide a Default Configuration" [Hack #2] , user scripts contain a section that describes what the script is and where it should run. Editing the @include and @exclude lines in this section is the first and easiest way to configure a user script, because the configuration travels with the script code. If you copy the file to someone else's computer or publish it online, other people will pick up the default configuration.
Another good time to alter a script's metadata is during installation. Remember in "Install a User Script" [Hack #1] when you first installed the Butler user script? Immediately after you select the Install User Script…menu item, Greasemonkey displays a dialog box titled Install User Script, which contains lists of the included and excluded pages, as shown in Figure 1-7.
Figure 1-7: Butler installation dialog
The two lists are populated with the defaults that are defined in the script's metadata section (specifically, the @include and @exclude lines), but you can change them to anything you like before you install the script. Let's say, for example, that you like Butler, but you have no use for it on Froogle, Google's cleverly named product comparison site. Before you install the script, you can modify the configuration to exclude that site but still let the script work on other Google sites.
To ensure that Butler doesn't alter Froogle, click the Add…button under "Excluded pages" and type the wildcard URL for Froogle, as shown in Figure 1-8.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Add or Remove Content on a Page
Use DOM methods to manipulate the content of a web page.
Since most user scripts center around adding or removing content from a web page, let's quickly review the standard DOM methods for manipulating content.
The following code adds a new element to the end of the page. The element will appear at the bottom of the page, unless you style it with CSS to position it somewhere else [Hack #7] :
	var elmNewContent = document.createElement('div');
	document.body.appendChild(elmNewContent)
You can also remove elements from a page. Removed elements disappear from the page (obviously), and any content after them collapses to fill the space the elements occupied. The following code finds the element with id="ads" and removes it:
	var elmDeleted = document.getElementById("ads");
	elmDeleted.parentNode.removeChild(elmDeleted);
If all you want to do is remove ads, it's probably easier to install the AdBlock extension than to write your own user script. You can download AdBlock at http://adblock.mozdev.org.
Many user scripts insert content into a page, rather than appending it to the end of the page. The following code creates a link to http://www.example.com and inserts it immediately before the element with id="foo":
	var elmNewContent = document.createElement('a');
	elmNewContent.href = 'http://www.example.com/';
	elmNewContent.appendChild(document.createTextNode('click here'));
	var elmFoo = document.getElementById('foo');
	elmFoo.parentNode.insertBefore(elmNewContent, elmFoo);
You can also insert content after an existing element, by using the nextSibling property:
	elmFoo.parentNode.insertBefore(elmNewContent, elmFoo.nextSibling);
Inserting new content before elmFoo.nextSibling will work even if elmFoo is the last child of its parent (i.e., it has no next sibling). In this case,
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Alter a Page's Style
There are four basic ways to add or modify a page's CSS rules.
In many of the user scripts I've written, I want to make things look a certain way. Either I'm modifying the page's original style in some way, or I'm adding content to the page and I want to make it look different from the rest of the page. There are several ways to accomplish this.
Here is a simple function that I reuse in most cases in which I need to add arbitrary styles to a page. It takes a single parameter, a string containing any number of CSS rules:
	function addGlobalStyle(css) {
		try {
			var elmHead, elmStyle;
			elmHead = document.getElementsByTagName('head')[0];
			elmStyle = document.createElement('style');
			elmStyle.type = 'text/css';
			elmHead.appendChild(elmStyle);
			elmStyle.innerHTML = css;
		} catch (e) {
			if (!document.styleSheets.length) {
				document.createStyleSheet();
			}
			document.styleSheets[0].cssText += css;
		}
	}
As you see in the previous example, Firefox maintains a list of the stylesheets in use on the page, in document.styleSheets (note the capitalization!). Each item in this collection is an object, representing a single stylesheet. Each stylesheet object has a collection of rules, and methods to add new rules or remove existing rules.
The insertRule method takes two parameters. The first is the CSS rule to insert, and the second is the positional index of the rule before which to insert the new rule:
	document.styleSheets[0].insertRule('html, body { font-size: large }', 0);
In CSS, order matters; if there are two rules for the same CSS selector, the later rule takes precedence. The previous line will insert a rule before all other rules, in the page's first stylesheet.
You can also delete individual rules by using the deleteRule method. It takes a single parameter, the positional index of the rule to remove. The following code will remove the first rule, which we just inserted with
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Master XPath Expressions
Tap into a powerful new way to find exactly what you're looking for on a page.
Firefox contains a little-known but powerful feature called XPath. XPath is a query language for searching the Document Object Model (DOM) that Firefox constructs from the source of a web page.
As mentioned in "Add or Remove Content on a Page" [Hack #6] , virtually every hack in this book revolves around the DOM. Many hacks work on a collection of elements. Without XPath, you would need to get a list of elements (for example, with document.getElementsByTagName) and then test each one to see if it's something of interest. With XPath expressions, you can find exactly the elements you want, all in one shot, and then immediately start working with them.
A good beginners' tutorial on XPath is available at http://www.zvon.org/xxl/XPathTutorial/General/examples.html.
To execute an XPath query, use the document.evaluate function. Here's the basic syntax:
	var snapshotResults = document.evaluate('XPath expression',
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
The function takes five parameters:
The XPath expression itself
More on this in a minute.
The root node on which to evaluate the expression
If you want to search the entire web page, pass in document. But you can also search just a part of the page. For example, to search within a <div id="foo">, pass document.getElementById("foo") as the second parameter.
A namespace resolver function
You can use this to create XPath queries that work on XHTML pages. See "Select Multiple Checkboxes" [Hack #36]
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Develop a User Script "Live"
Edit a user script and see your changes immediately.
While you're writing a user script, you will undoubtedly need to make changes incrementally and test the results. As shown in "Install a User Script" [Hack #1] , Greasemonkey stores your installed user scripts deep within your Firefox profile directory. Changes to these installed files take effect immediately, as soon as you refresh the page. This makes the testing cycle quick, because you can edit your partially written script, save changes, and refresh your test page to see the changes immediately.
Before you can take advantage of live editing, you need to set up file associations on your system, so that double-clicking a .user.js script opens the file in your text editor instead of trying to execute it or viewing it in a web browser.

Section 1.10.1.1: On Mac OS X.

Control-click a .user.js file in Finder, and then select Get Info. In the Open With section, select your text editor from the drop-down menu, or select Other…to find the editor program manually. Click Change All to permanently associate your editor with .js files.

Section 1.10.1.2: On Windows.

Right-click a .user.js file in Explorer, and then select Open With Choose Program. Select your favorite text editor from the list, or click Browse to find the editor application manually. Check the box titled "Always use the selected program to open this kind of file" and click OK.
Switch back to Firefox and select Tools Manage User Scripts. Select a script from the pane on the left and click Edit. If your file associations are set up correctly, this should open the user script in your text editor.
The first time you do this on Windows, you will get a warning message, explaining that you need to set up your file associations, as shown in Figure 1-9. You're one step ahead of the game, since you've already done this.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Debug a User Script
Learn the subtle art of Greasemonkey debugging.
The actual process of writing user scripts can be frustrating if you don't know how to debug them properly. Since JavaScript is an interpreted language, errors that would otherwise cause a compilation error (such as misspelled variables or function names) can only be caught when they occur at runtime. Furthermore, if something goes wrong, it's not immediately obvious how to figure out what happened, much less how to fix it.
If your user script doesn't appear to be running properly, the first place to check is JavaScript Console, which lists all script-related errors, including those specific to user scripts. Select Tools JavaScript Console to open the JavaScript Console window. You will probably see a long list of all the script errors on all the pages you've visited since you opened Firefox. (You'd be surprised how many high-profile sites have scripts that crash regularly.)
In the JavaScript Console window, click Clear to remove the old errors from the list. Now, refresh the page you're using to test your user script. If your user script is crashing or otherwise misbehaving, you will see the exception displayed in JavaScript Console.
If your user script is crashing, JavaScript Console will display an exception and a line number. Due to the way Greasemonkey injects user scripts into a page, this line number is not actually useful, and you should ignore it. It is not the line number within your user script where the exception occurred.
If you don't see any errors printed in JavaScript Console, you might have a configuration problem. Go to Tools Manage User Scripts and double-check that your script is installed and enabled and that your current test page is listed in the Included Pages list.
OK, so your script is definitely running, but it isn't working properly. What next? You can litter your script with alert calls, but that's annoying. Instead, Greasemonkey provides a logging function,
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Embed Graphics in a User Script
Add images to web pages without hitting a remote server.
A user script is a single file. Greasemonkey does not provide any mechanism for bundling other resource files, such as image files, along with the JavaScript code. While this might offend the sensibilities of some purists who would prefer to maintain separation between code, styles, markup, and media resources, in practice, it is rarely a problem for me.
This is not to say you can't include graphics in your scripts, but you need to be a bit creative. Instead of posting the image to a web server and having your user script fetch it, you can embed the image data in the script itself by using a data: URL. A data: URL allows you to encode an image as printable text, so you can store it as a JavaScript string. And Firefox supports data: URLs natively, so you can insert the graphic directly into a web page by setting an img element's src attribute to the data: URL string. Firefox will display the image without sending a separate request to any remote server.
You can construct data: URLs from your own image files at http://software.hixie.ch/utilities/cgi/data/data.
This user script runs on all pages. It uses an XPath query to find web bugs: 1 x 1-pixel img elements that advertisers use to track your movement online. The script filters this list of potential web bugs to include only those images that point to a third-party site, since many sites use 1 x 1-pixel images for spacing in table-based layouts.
There is no way for Greasemonkey to eliminate web bugs altogether; by the time a user script executes, the image has already been fetched. But we can make them more visible by changing the src attribute of the img element after the fact. The image data is embedded in the script itself.
Save the following user script as webbugs.user.js:
	// ==UserScript==
	// @name Web Bug Detector
	// @namespace http://diveintomark.org/projects/greasemonkey/
	// @description make web bugs visible
	// @include *
	// ==/UserScript==

	var snapImages = document.evaluate("//img[@width='1'][@height='1']",
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	for (var i = snapImages.snapshotLength - 1; i >= 0; i--) 
		var elmImage = snapImages.snapshotItem(i);
		var urlSrc = elmImage.src;
		var urlHost = urlSrc.replace(/^(.*?):\/\/(.*?)\/(.*)$/, "$2");
		if (urlHost == window.location.host) continue;
		elmImage.width = '80';
		elmImage.height = '80';
		elmImage.title = 'Web bug detected! src="' + elmImage.src + '"';
		elmImage.src =
	'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAA' +
	'AABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPB' +
	'oAAAv3SURBVHic7Zx5kFxVGcV%2FvWVmemaSSSYxiRkSgglECJK4gAtSEjQFLmjEUhEV' +
	'CxUs1NJyK5WScisKFbHUwo0q10jQABZoEJBEtgSdArNHkhCyTTKZzNo9S3dm6faPc2%2' +
	'FepNP9Xvfrnu5J2afqVXq6X7%2F3vXO%2F%2By3n3g5UUUUVVVRRRRVVVFFFFVVU4Y4p' +
	'lTbgTEUAmA%2F8CohU2JYzEtOBbwKDQH1lTTkVwQm%2Bfk0J7hEG5gCrgAFgYbFGlRIT' +
	'TWAT0EJx064BuMBc52nzetJgognsB6YBF%2BIvAQSAZuAa4F9AG4qFkwYTTWACxa3lwP' +
	'UU7olRYCbwduAp4Ih5P1wqA4vFRBOYBnqAHciLPl%2FgPRuBDwLDwEbgkHmdLq2Z%2Fh' +
	'Eqwz1GgVqgA7gT6AOey%2BN7ITRdvwfcAzxh3gsC2yfCUD%2BYaA8EEdgN7AceQuVISx' +
	'7fiwJXI2%2F7E5AETgC7mUQeWA4CAWLI89airPo1RJAbpgHXAr9HYaAfxdS%2BCbDPNw' +

	'%2FlInAM6ERJ4B%2FADbiXI0FE4CvQdE8gApPASIlsCqAYeyHwfeTlBVcK5SIQREAMuB' +
	'dNwVvJ3VXUAPOQlx5HU3cMDUB3kXZMAWYAVwG%2FBR43f%2B8C7qPASqGcBI4CXcBLKC' +
	'm8A1iR49wpwNko41rPA4ijsqhQBFHoaAE%2BhWbBPaizuRGFiVZgPbCaAsqkctdT%2FS' +
	'ie%2FRl4M%2FAdYAOnk1KPHjaOiB%2F2eb8aRNxS4L2oJKoFHjT3Pgr0mnvMQwkqiUSL' +
	'j5NHsnIjcKq5QAjFiySaRinzr59MOIySQBfwU%2BBHwGXA3zPOq0MExsy9Rgu4RxANwJ' +
	'tQFn8bsMhc6yHgAeAYGshONEgBc5%2F5wAHkmZ8xNrrCjcAw8DIUhy4zRtSah6vDSQwd' +
	'wH9R%2FEjl8YBxROJ2c7wfTSlLUgBN4Zeb80bNvbxQj8haBaxEAgTIy34OPILiaa%2Bx' +
	'2w6ORZuxfz5KVH0owbjWnG4E9iCPmYG6gB3m%2FSB6yFpE8BzgQ8CXgc%2BhntWNyCQw' +
	'ZB5gB3Almma2PAkjr59rHvSE2wOY714D%2FBhlbtAgPYPEh6fMtftwiMvm0SmUpMZQ%2' +
	'FE2i7B%2FAZbZ5xcABFJ%2B6jKENyPsiiMBe4EXg38DFaHqsA25BnpnrxnGk1GwFPgxc' +
	'DvxlnE1hROBe3ONfFHgdIi9hrrEVJapBlLF7EHmDeHtyGmhHg3YOiqFFEWgvOmSO48gD' +
	'a8wRRVOnFnjSGH6TeYjbgbvRIGRiyDzQduQNq1AcTBqbapEK00vuui%2BCptudxpavoM' +
	'GM4xTu4zN4Iegxdi1AuWAiiveTCKOC9CykulwF%2FBARtwf4pPk8E03A61H91QHMMu9P' +
	'R3ExjfrgpizfDSLy7jbn3Y48caE5v1Q9fhQls0CuE0pRB46ikW4DDiIv%2FBtSXoZRSd' +
	'ABfCPDEBsLd6FYusLYYz0LHE%2FIxEwUO28AHkZlSSfKrn3kl3TywZC5bs5lhFKrMQlz' +
	'0zHzuhWRsRDFuQGUZNLmnCZExuWI7EdRnL0Seee9wDZOJXEqcBHwCxSvbkGZ9jDeCccP' +
	'bBlVVgGjGTgPuAR4F%2FAYDmmfwBm4eebzFIqvc1C5tNac%2Fx4U3yxqgVcjr%2B0Hrg' +
	'NemXHOGY0gSiJxFPz%2FA3wJtW33IVJOoDhZi7zzrah4TaN4uQSVIGngUhxywsBinDLp' +
	'W8gTvVSdMwZn43haN%2Bov%2B8zfu4BPA79GD78ZdQUJ8%2Fn4ox15Vwp1E1EUO1tQvE' +
	'ujcukS5OkVRc7sUgBCaL3jDpRBN6LKvwfFt5sQEWPA%2FUBzOBx%2By4IFC0KLFy8mGo' +
	'0SCoUIBAKMjIywd%2B9e9uzZw%2FDwMCiAfwR4HmX2j6LB%2BCyqEduosLhaLIEtwF2o' +
	'5%2BxHGfdpVId1ofKlGbWCNwLT6uvrWbZsGXV1dYTD4axHIBBg9%2B7dtLa2gjxyDfAF' +
	'c92bEaH78S8ylAx%2Bs3AjmpJ%2FQEH9eeDbqDXrRBnxCMq6tmcOhUKh5YsWLQJgdHT0' +
	'5DE2NnbK36lUiubmZmKxGPF4vBFlZIDvApvQ4lICxcXXosG5FrV0s4EXKJ3w6opCPXA%' +
	'2B6nc%2FhnrkBPAbFPsGUL1nJSI7tWYj6WpNfX19ePbs2ad4WyQSyemJfX19bNy40d57' +
	'DfAT5JHLUYZeiVq%2BTPQjve9nqAyaMORL4BvQFHo3jmK7BRl4DCWNdnNktk7NaEVtaV' +
	'1dHQ0NDZ7EjSdw27ZtADtRsbwMhYMGc%2B2jwD%2BRYNCISqdFwLmoRAL16avNOTvITz' +
	'HKG14Engf8DmU8iwGUTTcgsjpQMO8me0D%2FIkowhEIhIpFIXuRFIhEOHz5MZ2cn6KFt' +
	'15RE03g9IjaJCt0IksHsMzWgyuAcNM2XoiJ%2FCyrwn0XkF7VE4EZgANVlvxxn%2BKOo' +
	'hOhFWfYo2b3Ooha1dVdkfuBGXDgcJh6P093dDSInjDTHx1GWH0Dhw6pFKTR4EaQWRc1r' +
	'e6TN%2B8uRanQxas%2B6UdeTzzp1VripMQHUNg2i%2BuuvqEBOoGl7BBHpNiWa0OifBp' +
	'swQJ5pyQsGgyQSCUZGTuaAl1BNuRNnda4fkdePCvMRY69deK8zttfiyPonkNc9a85bgh' +
	'LgH1FB7ke1cfXAEFo5C6Asm0SFcQciMJ8bXo1iFyC1cyZ6Iiv5dnpfI21sGEbZdwOSrm' +
	'Ie37Oibx3yNqtlNuLIcSnUSiaR%2BFEwvDywAZHVhZ61F7l9PoE4hBPsmY8C6hwUqBLm' +
	'ggdQRZwDMTQAAyiBvAb10nfgTWDa3CaBws0URGATIrEOJbi1qNhfjjqkguBWB4aQ0HkQ' +
	'PWO7MSTfyj%2BCSozrpqMs9CpE4AzzFFbos%2FMxAymk852P%2Bt91SIBdaU5fn6cdFm' +
	'NoGtulURsjpqG4ugL14AXBTQ8MINcfRIE87uPaXcDYAhRw5qLWpQUJgNOR5HtW9u8%2F' +
	'gLIlKIT0oDi4CRXNfkWENHqmDmNfL%2FLOA%2FhY5vUisAGl%2FhEKr59SKG71zELDPB' +
	'cRNg%2FJz43mmMppwXgfKpWS5ivdSO7qRwmlBXVCxWAUzapO5J07ya5%2BuyIfDxzAX8' +
	'85gozcnUJD3IyU1bk4kTyIRmhcXDiA2sI2NK3sIk87IvEQzsJVsbtVR3FifAM%2BMrEX' +
	'gSH87wxIG4MeO4TmzAk0bYPmgifMsc%2F5zmbg66jmG0IZ8kWUCOLoQXuQthgCfkDxgs' +
	'ggInEEH78AcJvzltw0%2FlWPHuCZvbBjMyxtRKyA5k0XitrHJVFZ%2Bd7WmOej%2Bmw1' +
	'jmf0mq9NRyRej6b0Op%2F2jbfTToqC4FXG2Crfr7LRjx74tgfh1gOw5FI0zAeBVti3Xz' +
	'uktmPiJZKpksD7jA32M1Cs6kLR4GGkdN%2BFem0%2Fm44sxtDAFSzQehG4CU3hQvamjM' +
	'coiluHga9uhdlb4YIA9KX1XifODqweFOd6UZ65wnzexqkzwE7l6aiGuxmtznnuY%2FHA' +
	'EI76nbdImw%2BBtozxi%2BMoh9j1kGNpZ6XL7hnsNscICh1LkKryhHlv%2FAxIm2vOQr' +
	'XgG9HaSbEEQm5BJCe8CLS7qYohMI1i2hCqoW2cGTLXzszydUiyAklR%2FVmuOYhIbAJu' +
	'QwvwtmYtBgUvD%2BRDYIdvcxykcLwshKOeZEMUEb0PKTnZtoakjV1NyGM3IVXl%2FhLY' +
	'WhC8ypgenMRZKrjtLbQK0EU4slmu%2B9u9OnG0PW5Vac3MD14EHiuXIQY1qF0%2BFy0T' +
	'eAkXveY4jmJpOX73cgrcCByl9N7nhSjypA0o02aLf%2BMxhKMJbqECP8h2IzBb7JloNA' +
	'LvRBJWjPwGsBeR2Immf1nhRmBJF1%2FyQA2S2ttRMT1%2BZc8NdkNTFxX4MXY5f%2Bbg' +
	'hXrgA2jLRwzv6WsxZs61G0HLislE4Cwk1jyHkkche%2FxilGkhPROThcBatOb8CCIvX%' +
	'2B%2BzsAtNZd8nM1kIjKL1jg2oOyl0o2QKxc6ye%2BFk%2BOV3AP3wcC%2FyPq%2FFol' +

	'yIUYH%2FEmUyeGAt2jz0JO6dhxeK0S19YzIQ2IBqTvtjmEnzY%2Bp8UGkCA0i6egFNwU' +
	'oU70Wh0jEwitTnEIWXLpMClfbAqSjwx%2FCfPCqKShIYQr3vIJL3ixFt%2Fy8RRsJp9b' +
	'%2B0q6KKKqqooooqfOB%2F6MmP5%2BlO7YkAAAAASUVORK5CYII%3D';
	}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Avoid Common Pitfalls
Learn the history of Greasemonkey security and how it affects you now.
Once upon a time, there was a security hole. (This is not your standard fairy tale. Stay with me.) Greasemonkey's architecture has changed substantially since it was first written. Version 0.3, the first version to gain wide popularity, had a fundamental security flaw: it trusted the remote page too much when it injected and executed user scripts.
Back in those days, Greasemonkey's injection mechanism was simple, elegant…and wrong. It initialized a set of API functions as properties of the global window object, so that user scripts could call them. Then, it determined which user scripts ought to execute on the current page based on the @include and @exclude parameters. It loaded the source code of each user script, created a <Script> element, assigned the source code of the user script to the contents of the <Script> element, and inserted the element into the page. Once all the user scripts finished, Greasemonkey cleaned up the page by removing the <Script> elements it had inserted and removing the global properties it had added.
Simple and elegant, to be sure; so why was it wrong?
The answer lies in the largely untapped power of the JavaScript language and the Document Object Model (DOM). JavaScript running in a browser is not simply a scripting language. The browser sets up a complex object hierarchy for scripts to manipulate the web page, and a complex event model to notify scripts when things happen.
This leads directly to the first security hole. When Greasemonkey 0.3 inserted a user script into a page, this triggered a DOMNodeInserted event, which the remote page could intercept. Consider a web page with the following JavaScript code. Keep in mind, this is not a user script; this is just regular JavaScript code that is part of the web page in which user scripts are executing.
	<script type="text/javascript>
	_scripts = [];
	_c = document.getElementsByTagName("script").length;
	function trapInsertScript(event) {
		var doc = event.currentTarget;
		var arScripts = doc.getElementsByTagName("script");
		if (arScripts.length > _numPreviousScripts) {
			_scripts.push(arScripts[_c++].innerHTML);
		}

		}
		document.addEventListener("DOMNodeInserted", trapInsertScript, true);
		</script>
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: Linkmania!
The Web revolves around links. Links take you to a site, let you navigate within it, and finally take you somewhere else. But not all links are created equal. Some links launch a new window without your permission. Some launch external applications. Some execute a piece of JavaScript code, which means they could do almost anything. And some links aren't even clickable.
The first step to reclaiming your browser is taking control of links.
Make every URL clickable.
Have you ever visited a page that displayed a naked URL that you couldn't click? That is, the URL is displayed as plain text on the page, and you need to manually copy the text and paste it into a new browser window to follow the link. I run into this problem all the time while reading weblogs, because many weblog publishing systems allow readers to submit comments (including URLs) but just display the comment verbatim without checking whether the comment includes a naked URL. This hack turns all such URLs into clickable links.
This user script runs on all pages. To ensure that it does not affect URLs that are already linked, it uses an XPath query that includes not(ancestor::a). To ensure that it does affect URLs in uppercase, the XPath query also includes "contains(translate(., 'HTTP', 'http'), 'http')]".
Once we find a text node that definitely contains an unlinked URL, there could be more than one URL within it, so we need to convert all the URLs while keeping the surrounding text intact. We replace the text with an empty <span> element as a placeholder and then incrementally reinsert each non-URL text snippet and each constructed URL link.
Save the following user script as linkify.user.js:
	// ==UserScript==
	// @name		  Linkify
	// @namespace	  http://youngpup.net/userscripts
	// @description   Turn plain-text URLs into hyperlinks
	// @include *
	// ==/UserScript==

	// based on code by Aaron Boodman
	// and included here with his gracious permission

	var urlRegex = /\b(https?:\/\/[^\s+\"\<\>]+)/ig;
	var snapTextElements = document.evaluate("//text()[not(ancestor::a) " + 
		"and not(ancestor::script) and not(ancestor::style) and " + 
		"contains(translate(., 'HTTP', 'http'), 'http')]", 
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	for (var i = snapTextElements.snapshotLength - 1; i >= 0; i--) {
		var elmText = snapTextElements.snapshotItem(i);
		if (urlRegex.test(elmText.nodeValue)) {
			var elmSpan = document.createElement("span");
			var sURLText = elmText.nodeValue;
			elmText.parentNode.replaceChild(elmSpan, elmText);
			urlRegex.lastIndex = 0;
			for (var match = null, lastLastIndex = 0;
				 (match = urlRegex.exec(sURLText)); ) { 
				elmSpan.appendChild(document.createTextNode(
				sURLText.substring(lastLastIndex, match.index))); 
				var elmLink = document.createElement("a"); 
				elmLink.setAttribute("href", match[0]); 
				elmLink.appendChild(document.createTextNode(match[0])); 
				elmSpan.appendChild(elmLink); 
				lastLastIndex = urlRegex.lastIndex;
			}
			elmSpan.appendChild(document.createTextNode(
				sURLText.substring(lastLastIndex)));
			elmSpan.normalize();
		}
	}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Hacks 13–20: Introduction
The Web revolves around links. Links take you to a site, let you navigate within it, and finally take you somewhere else. But not all links are created equal. Some links launch a new window without your permission. Some launch external applications. Some execute a piece of JavaScript code, which means they could do almost anything. And some links aren't even clickable.
The first step to reclaiming your browser is taking control of links.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Turn Naked URLs into Hyperlinks
Make every URL clickable.
Have you ever visited a page that displayed a naked URL that you couldn't click? That is, the URL is displayed as plain text on the page, and you need to manually copy the text and paste it into a new browser window to follow the link. I run into this problem all the time while reading weblogs, because many weblog publishing systems allow readers to submit comments (including URLs) but just display the comment verbatim without checking whether the comment includes a naked URL. This hack turns all such URLs into clickable links.
This user script runs on all pages. To ensure that it does not affect URLs that are already linked, it uses an XPath query that includes not(ancestor::a). To ensure that it does affect URLs in uppercase, the XPath query also includes "contains(translate(., 'HTTP', 'http'), 'http')]".
Once we find a text node that definitely contains an unlinked URL, there could be more than one URL within it, so we need to convert all the URLs while keeping the surrounding text intact. We replace the text with an empty <span> element as a placeholder and then incrementally reinsert each non-URL text snippet and each constructed URL link.
Save the following user script as linkify.user.js:
	// ==UserScript==
	// @name		  Linkify
	// @namespace	  http://youngpup.net/userscripts
	// @description   Turn plain-text URLs into hyperlinks
	// @include *
	// ==/UserScript==

	// based on code by Aaron Boodman
	// and included here with his gracious permission

	var urlRegex = /\b(https?:\/\/[^\s+\"\<\>]+)/ig;
	var snapTextElements = document.evaluate("//text()[not(ancestor::a) " + 
		"and not(ancestor::script) and not(ancestor::style) and " + 
		"contains(translate(., 'HTTP', 'http'), 'http')]", 
		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
	for (var i = snapTextElements.snapshotLength - 1; i >= 0; i--) {
		var elmText = snapTextElements.snapshotItem(i);
		if (urlRegex.test(elmText.nodeValue)) {
			var elmSpan = document.createElement("span");
			var sURLText = elmText.nodeValue;
			elmText.parentNode.replaceChild(elmSpan, elmText);
			urlRegex.lastIndex = 0;
			for (var match = null, lastLastIndex = 0;
				 (match = urlRegex.exec(sURLText)); ) { 
				elmSpan.appendChild(document.createTextNode(
				sURLText.substring(lastLastIndex, match.index))); 
				var elmLink = document.createElement("a"); 
				elmLink.setAttribute("href", match[0]); 
				elmLink.appendChild(document.createTextNode(match[0])); 
				elmSpan.appendChild(elmLink); 
				lastLastIndex = urlRegex.lastIndex;
			}
			elmSpan.appendChild(document.createTextNode(
				sURLText.substring(lastLastIndex)));
			elmSpan.normalize();
		}
	}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Force Offsite Links to Open in a New Window
Keep your browser organized by automatically opening each site in its own window.
I originally wrote this user script after someone posted a request to the Greasemonkey script repository. I personally like to open links in a new tab in the current window, but some people prefer to open a separate window for each site. Offsite Blank lets you do this automatically, by forcing offsite links to open in a new window.
This user script runs on remote web sites (but not, for example, on HTML documents stored on your local machine that you open from the File Open menu). Since search engines exist to provide links to other pages, and I find it annoying for search result links to open new links, I've excluded Google and Yahoo! by default.
The code itself breaks down into four steps:
  1. Get the domain of the current page.
  2. Get a list of all the links on the page.
  3. Compare the domain of each link to the domain of the page.
  4. If the domains don't match, set the target attribute of the link so that it opens in a new window.
Save the following user script as offsiteblank.user.js:
	// ==UserScript==
	// @name		  Offsite Blank
	// @namespace	  http://diveintomark.org/projects/greasemonkey/
	// @description	  force offsite links to open in a new window
	// @include		  http*://*
	// @exclude       http://*.google.tld/*
	// @exclude       http://*.yahoo.tld/*
	// ==/UserScript==

	var sCurrentHost = location.host;
	var arLinks = document.links;
	for (var i = arLinks.length - 1; i >= 0; i--) {
		var elmLink = arLinks[i];
		if (elmLink.host && elmLink.host != sCurrentHost) {
				elmLink.target = "_blank";
		}
	}
After installing the user script (Tools Install This User Script), go to
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Fix Broken Pop-up Links
Change javascript: pseudo-protocol pop-up windows into normal hyperlinks.
Advanced browser users do more than just click hyperlinks. They also right-click them, print them, and save them to disk. All these additional behaviors are broken when web developers incorrectly use the javascript: pseudo-protocol to create pop-up windows.
A broken pop up looks like this:
	<a href="javascript:popup('http://youngpup.net/')">go to youngpup.net</a>
In this example, the web developer is attempting to create a link that opens in a new pop-up window. Unfortunately, the value of the href attribute is not a valid URL. It's just JavaScript, which works only in the context of the current page. This means that if a user right-clicks the link and tries to open it in a new window, the popup function is undefined and the user gets an error message. Likewise, if the user attempts to save or print the contents of the hyperlink, the browser first has to download it, which it cannot do because the href doesn't contain a URL; it contains a random JavaScript statement.
There is no reason for web developers ever to do this. You can easily write annoying pop-up windows and retain the benefits of regular hyperlinks, by adding an onclick handler to a regular hyperlink:
	<a href="http://youngpup.net/" 
           onclick="window.open(this.href); return false;"> 
		 go to youngpup.net 
	</a> 
Using Greasemonkey, we can scan for javascript: links that appear to open pop-up windows and then change them to use an onclick handler instead.
This user script runs on every page. It loops through every link on the page, looking for javascript: URLs. If the link's href attribute begins with javascript:, the script checks whether it appears to open a pop-up window by looking for something that looks like a URL after the javascript: keyword. Since the overwhelming majority of web authors that use "javascript:" links use them to open pop-up windows, this should not have too many false positives.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Remove URL Redirections
Cut out the middleman and make links point directly to where you want to go.
Many portal sites use redirection links for links that point to other sites. The link first goes to a tracking page on the portal, which logs your click and sends you on your way to the external site. Not only is this an invasion of privacy, but it's also slower, since you need to load the tracking page before you are redirected to the page you actually want to read. This hack detects such redirection links and converts them to direct links that take you straight to the final destination.
This user script runs on all pages, except for a small list of pages where it is known to cause problems with false positives. It uses the document.links collection to find all the links on the page and checks whether the URL of the link includes another URL within it. If it finds one, it extracts it and unescapes it, and replaces the original URL.
Save the following user script as nomiddleman.user.js:
	// ==UserScript==
	// @name        NoMiddleMan
	// @namespace   http://0x539.blogspot.com/
	// @description Rewrites URLs to remove redirection scripts
	// @include     *
	// @exclude		http://del.icio.us/*
	// @exclude     http://*bloglines.com/*
	// @exclude		http://web.archive.org/*
	// @exclude		http://*wists.com/*
	// ==/UserScript==

	// based on code by Albert Bachand
	// and included here with his gracious permission
	// http://kungfoo.webhop.org/nomiddleman.user.js

	for (var i=0; i<document.links.length; i++) { 
		 var link, temp, start, url, qindex, end; 
		 link = document.links[i];

		 // Special case for Google results (assumes English language)
		 if (link.text == 'Cached' ||
			 /Similar.*?pages/.exec(link.text)) {
			 continue;
		 }

		 temp = link.href.toLowerCase();

		 // ignore javascript links and GeoURL
		 if (temp.indexOf('javascript:') == 0 ||
		 temp.indexOf('geourl.org') != -1) {
			 continue;
		 }

		 // find the start of the (last) real url
		 start = Math.max(temp.lastIndexOf('http%3a'),
				  temp.lastIndexOf('http%253a'),
				  temp.lastIndexOf('http:'));

		 if (start <= 0) { 
			 // special case: handle redirect url without a 'http:' part 
			 start = link.href.lastIndexOf('www.'); 
			 if (start < 10) {
				 start = 0;
			 } else { 
				 link.href = link.href.substring(0, start) + 
				 'http://' + link.href.substring(start);
			 }
		 }

		 // we are most likely looking at a redirection link
		 if (start > 0) {
			 url = link.href.substring(start);

			 // check whether the real url is a parameter 
			 qindex = link.href.indexOf('?'); 
			 if (qindex > -1 && qindex < start) {
			     // it's a parameter, extract only the url
				 end = url.indexOf('&');
				 if (end > -1) {
				 url = url.substring(0, end);
				 }
			 }
			 // handle Yahoo's chained redirections
			 var temp = url;
			 url = unescape(url);

			 while (temp != url) {
				 temp = url;
				 url = unescape(url);
			 }
			 // and we're done
			 link.href = url.replace(/&amp;/g, '&');
		 }
	}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Warn Before Opening PDF Links
Make your browser double-check that you really want to open that monstrous PDF.
How many times has this happened to you? You're searching for something, or just browsing, and click on a promising-looking link. Suddenly, your browser slows to a crawl, and you see the dreaded "Adobe Acrobat Reader" splash screen. Oh no, you've just opened a PDF link, and your browser is launching the helper application from hell.
This hack saves you the trouble, by popping up a dialog box when you click on a PDF file to ask you if you're sure you want to continue. If you cancel, you're left on the original page and can continue browsing in peace.
This hack is derived from a Firefox extension called TargetAlert, which offers more features and customization options. Download it at http://bolinfest.com/targetalert/.
This user script runs on all pages. It iterates through the document.links collection, looking for links pointing to URLs ending in .pdf. For each link, it attaches an onclick handler that calls the window.confirm function to ask you if you really want to open the PDF document.
Save the following user script as pdfwarn.user.js:
	// ==UserScript==
	// @name          PDF Warn
	// @namespace     http://www.sencer.de/
	// @description   Ask before opening PDF links
	// @include       *
	// ==/UserScript==

	// based on code by Sencer Yurdagül and Michael Bolin
	// and included here with their gracious permission
	// http://www.sencer.de/code/greasemonkey/pdfwarn.user.js

	for (var i = document.links.length - 1; i >= 0; i--) {
		var elmLink = document.links[i];
		if (elmLink.href && elmLink.href.match(/^[^\\?]*pdf$/i)) {
			var sFilename = elmLink.href.match(/[^\/]+pdf$/i); 
			elmLink.addEventListener('click', function(event) {
				if (!window.confirm('Are you sure you want to ' + 
						'open the PDF file "' + 
						sFilename + '"?')) {


						event.stopPropagation();
						event.preventDefault();
				}
			}, true);
		}
	}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Avoid the Slashdot Effect
Add web cache links to Slashdot articles.
Reading Slashdot is one of my guilty pleasures. It is a guilty pleasure that I share with tens of thousands of other tech geeks. People who have been linked from a Slashdot article report that Slashdot sends as many as 100,000 visitors to their site within 24 hours. Many sites cannot handle this amount of traffic. In fact, the situation of having your server crash after being linked from Slashdot is known as the Slashdot effect.
Read more about the Slashdot effect at http://en.wikipedia.org/wiki/Slashdot_effect.
This hack tries to mitigate the Slashdot effect by adding links to Slashdot articles that point to various global web caching systems. Instead of visiting the linked site, you can view the same page through a third-party proxy. If the Slashdot effect has already taken hold, the linked page might still be available in one of these caches.
This user script runs on all Slashdot pages, including the home page. The script adds a number of CSS rules to the page to style the links we're about to add. Then, it constructs three new links—one to Coral Cache, one to MirrorDot, and one to the Google Cache—and adds them after each external link in the Slashdot article.
Save the following user script as slashdotcache.user.js:
	// ==UserScript==
	// @name        Slashdot Cache
	// @namespace   http://www.cs.uni-magdeburg.de/~vlaube/Projekte/
	GreaseMonkey/
	// @description Adds links to web caches on Slashdot
	// @include     http://slashdot.tld/*
	// @include     http://*.slashdot.tld/*
	// ==/UserScript==

	// based on code by Valentin Laube
	// and included here with his gracious permission

	var coralcacheicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA'+ 
	'oAAAAKCAYAAACNMs%2B9AAAAgUlEQVQY042O0QnCQBQEZy0sFiEkVVxa8GxAuLOLgD3cV'+ 
	'RKwAytYf05JkGgGFt7H8nZkG10UgBNwZE0F7j77JiIJGPlNFhGzgwOQd%2FQytrEJdjtb'+ 
	'rs%2FORAqRZBvZBrQxby2nv5iHniqokquUgM%2FH8Hadh57HNG05rlMgFXDL0vE%2FL%2'+ 
	'BEXVN83HSenAAAAAElFTkSuQmCC'; 

	var mirrordoticon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAo'+ 
	'AAAAKCAYAAACNMs%2B9AAAAbklEQVQY05WQMRKEMAwDNzzqUobWv%2BBedvcK3EKZV4km'+ 
	'BiYFE9RYI3mssZIkRjD1Qnbfsvv2uJjdF6AApfELkpDEZ12XmHcefpJEiyrAF%2Fi1G8H'+ 
	'3ajZPjOJVdPfMGV3N%2FuGlvseopprNdz2NFn4AFndcO4mmiYkAAAAASUVORK5CYII%3D'; 
	var googleicon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAA'+ 
	'AKCAIAAAACUFjqAAAAiklEQVQY02MUjfmmFxPFgAuIxnz7jwNcU9BngSjae%2FbDxJUPj'+ 
	'1z%2BxMDAYKPLlx8u72wswMDAwASRnrjyIQMDw%2BoW3XfbbfPD5SFchOGCHof2nHmPaT'+ 
	'gTpmuEPA8LeR6GsKHSNrp8E1c%2B3Hv2A8QKG10%2BiDjUaRD7Qmsuw51GlMcYnXcE4Aq'+ 
	'SyRn3Abz4culPbiCuAAAAAElFTkSuQmCC'; 
	var backgroundimage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA'+ 
	'DEAAAAOCAYAAACGsPRkAAAAHXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBUaGUgR0lNUO'+ 
	'9kJW4AAAC7SURBVEjH7daxDYMwEEbhh11cAxKSKYEV0qeKMgETZBbPkgmYIEqVPisAJZa'+ 
	'QTOPCUprQZYAY8Sb4P11zGcD9dT0BFuhIpx6wt%2FPjnX0BTxEpjako8uLv1%2FvV49xM'+ 
	'CGEBLgqwIlI2dZsEAKDIC5q6RURKwCqgM6ZCa01Kaa0xpgLo1CZLsW23YgcdiANxIH4g%'+ 
	'2FOqTHL%2FtVkDv3EyMMSlAjBHnZoBeATaEsIzTkMxF%2FOoZp2F7O2y2hwfwA3URQvMn'+ 
	'dliTAAAAAElFTkSuQmCC';

	function addGlobalStyle(css) { 
		var head, style; 
		head = document.getElementsByTagName('head')[0]; 
		if (!head) { return; } 
		style = document.createElement('style'); 
		style.type = 'text/css'; 
		style.innerHTML = css; 
		head.appendChild(style);
	}

	addGlobalStyle('' +
	'a.coralcacheicon, a.mirrordoticon, a.googleicon { \n' +
	' padding-left: 15px; background: center no-repeat; \n' +
	'} \n' +
	'a.coralcacheicon { \n' +
	' background-image: url(' +coralcacheicon + '); \n' +
	'} \n' +
	'a.mirrordoticon { \n' +
	' background-image: url(' + mirrordoticon + '); \n' +
	'} \n' +
	'a.googleicon { \n' +
	' background-image: url(' + googleicon + '); \n' +
	'} \n' +
	'a.coralcacheicon:hover, a.mirrordoticon:hover, ' +
	'a.googleicon:hover { \n' +
	' opacity: 0.5; \n' +
	'} \n' +
	'div.backgroundimage { \n' +
	'  display:inline; \n' +
	'  white-space: nowrap; \n' +
	'  padding:3px; \n' +
	'  background:url(' +  backgroundimage + ') center no-repeat; \n' +
	'}');

	var link, anchor, background;
	for (var i=0; i<document.links.length; i++) {

	link = document.links[i];

	// filter relative links
	if(link.getAttribute('href').substring(0,7) != 'http://') {
		continue;
	}

	// filter all other links    
	if(link.parentNode.nodeName.toLowerCase() != 'i' &&       
       (link.parentNode.nodeName.toLowerCase() != 'font' || 
	   link.parentNode.color != '#000000' || link.parentNode.size == '2') 
&&
	   (!link.nextSibling || !link.nextSibling.nodeValue ||
	   link.nextSibling.nodeValue.charAt(1) != '[')) {
	   continue;

	}
	
	// add background
	background = document.createElement('div');
	background.className = 'backgroundimage';
	link.parentNode.insertBefore(background, link.nextSibling);

	//add mirrordot link
	anchor = document.createElement('a');
	anchor.href = 'http://www.mirrordot.com/find-mirror.html?' + link.href;
	anchor.title = 'MirrorDot - Solving the Slashdot Effect';
	anchor.className = 'mirrordoticon';
	background.appendChild(anchor);

	//add coral cache link
	anchor = document.createElement('a');
	anchor.href = link.href;
	anchor.host += '.nyud.net:8090';
	anchor.title = 'Coral - The NYU Distribution Network';
	anchor.className = 'coralcacheicon';
	background.appendChild(anchor);

	//add google cache link
	anchor = document.createElement('a');
	anchor.href = 'http://www.google.com/search?q=cache:' + link.href;
	anchor.title = 'Google Cache';
	anchor.className = 'googleicon';
	background.appendChild(anchor);

	// add a space so it wraps nicely 
	link.parentNode.insertBefore(document.createTextNode(' '), 
		link.nextSibling);
}
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Convert UPS and FedEx Tracking Numbers to Links
Make it easier to track packages.
All major package-delivery companies have web sites that allow you to track the status of packages. This is especially useful for online shoppers. Unless you're buying downloadable software, pretty much everything you buy online needs to be shipped one way or another. Unfortunately, not all online retailers are as web-savvy as one might hope.
This hack scans web pages for package tracking numbers and then converts them to links that point to the page on the delivery company's web site that shows the shipment's current status.
This user script runs on all pages. It is similar to "Turn Naked URLs into Hyperlinks" [Hack #13] . It scans the page for variations of package numbers that are not already contained in an <a> element and then constructs a link that points to the appropriate online tracking site.
These patterns are converted into links to UPS (http://www.ups.com ):
  • 1Z 999 999 99 9999 999 9
  • 9999 9999 999
  • T999 9999 999
This pattern is converted into a link to FedEx (http://www.fedex.com):
  • 9999 9999 9999
The following patterns are converted into links to the United States Postal Service (http://www.usps.com ):
  • 9999 9999 9999 9999 9999 99
  • 9999 9999 9999 9999 9999
Save the following user script as tracking-linkify.user.js:
	// ==UserScript==
	// @name UPS/FedEx Tracking Linkify
	// @namespace http://scripts.slightlyinsane.com
	// @description Link package tracking numbers to appropriate site
	// @include *
	// ==/UserScript==

	// Based on code by Justin Novack and Logan Ingalls
	// and included here with their gracious permission
	// Originally licensed under a Create Commons license
	// Visit http://creativecommons.org/licenses/by-sa/2.0/ for details

	var UPSRegex = new RegExp('/\b(1Z ?[0-9A-Z]{3} ?[0-9A-Z]{3} ?[0-9A-Z]{'+
	'2} ?[0-9A-Z]{4} ?[0-9A-Z]{3} ?[0-9A-Z]|[\\dT]\\d\\d\\d ?\\d\\d\\d\\d '+
	'?\\d\\d\\d)\\b', 'ig');
	var FEXRegex = new RegExp('\\b(\\d\\d\\d\\d ?\\d\\d\\d\\d ?\\d\\d\\d\\'+
	'd)\\b', 'ig');
	var USARegex = new RegExp('\\b(\\d\\d\\d\\d ?\\d\\d\\d\\d ?\\d\\d\\d\\'+
	'd ?\\d\\d\\d\\d ?\\d\\d\\d\\d ?\\d\\d|\\d\\d\\d\\d ?\\d\\d\\d\\d ?\\d'+
	'\\d\\d\\d ?\\d\\d\\d\\d ?\\d\\d\\d\\d)\\b', 'ig');

	function UPSUrl(t) {
		return 'http://wwwapps.ups.com/WebTracking/processInputRequest?sor'+
			't_by=status&tracknums_displayed=1&TypeOfInquiryNumber=T&loc=e'+
			'n_US&InquiryNumber1=' + String(t).replace(/ /g, '') +
			'&track.x=0&track.y=0';
	}

	function FEXUrl(t) {
		return 'http://www.fedex.com/cgi-bin/tracking?action=track&languag'+
			'e=english&cntry_code=us&initial=x&tracknumbers=' +
			String(t).replace(/ /g, '');
	}

	function USAUrl(t) {
	return 'http://trkcnfrm1.smi.usps.com/netdata-cgi/db2www/cbd_243.d'+

		'2w/output?CAMEFROM=OK&strOrigTrackNum=' +
		String(t).replace(/ /g, '');
	}

	// tags we will scan looking for un-hyperlinked urls
	var allowedParents = [
		'abbr', 'acronym', 'address', 'applet', 'b', 'bdo', 'big',
		'blockquote', 'body', 'caption', 'center', 'cite', 'code',
		'dd', 'del', 'div', 'dfn', 'dt', 'em', 'fieldset', 'font',
		'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'i', 'iframe',
		'ins', 'kdb', 'li', 'object', 'pre', 'p', 'q', 'samp',
		'small', 'span', 'strike', 's', 'strong', 'sub', 'sup',
		'td', 'th', 'tt', 'u', 'var'];

	var xpath = '//text()[(parent::' + allowedParents.join(' or parent::') +
		')]';

	var candidates = document.evaluate(xpath, document, null,
		XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

	//var tO = new Date().getTime();
	for (var cand = null, i = 0; (cand = candidates.snapshotItem(i)); i++) {

	// UPS Track
	if (UPSRegex.test(cand.nodeValue)) {
		var span = document.createElement('span');
		var source = cand.nodeValue;

		cand.parentNode.replaceChild(span, cand);
	
		UPSRegex.lastIndex = 0;
		for (var match = null, lastLastIndex = 0;
			 (match = UPSRegex.exec(source)); ) {
			span.appendChild(document.createTextNode(
				source.substring(lastLastIndex, match.index)));

			var a = document.createElement('a');
			a.setAttribute('href', UPSUrl(match[0]));
			a.setAttribute('title', 'Linkified to UPS');
			a.appendChild(document.createTextNode(match[0]));
			span.appendChild