<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/css" href="http://commons.oreilly.com/wiki/skins/common/feed.css?97"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
	<channel>
		<title>Greasemonkey Hacks/Search - Revision history</title>
		<link>http://commons.oreilly.com/wiki/index.php?title=Greasemonkey_Hacks/Search&amp;action=history</link>
		<description>Revision history for this page on the wiki</description>
		<language>en</language>
		<generator>MediaWiki 1.11.0</generator>
		<lastBuildDate>Thu, 23 May 2013 01:36:56 GMT</lastBuildDate>
		<item>
			<title>Docbook2Wiki: Initial conversion from Docbook</title>
			<link>http://commons.oreilly.com/wiki/index.php?title=Greasemonkey_Hacks/Search&amp;diff=9635&amp;oldid=prev</link>
			<description>&lt;p&gt;Initial conversion from Docbook&lt;/p&gt;

			&lt;table style=&quot;background-color: white; color:black;&quot;&gt;
			&lt;col class='diff-marker' /&gt;
			&lt;col class='diff-content' /&gt;
			&lt;col class='diff-marker' /&gt;
			&lt;col class='diff-content' /&gt;
			&lt;tr&gt;
				&lt;td colspan='2' style=&quot;background-color: white; color:black;&quot;&gt;←Older revision&lt;/td&gt;
				&lt;td colspan='2' style=&quot;background-color: white; color:black;&quot;&gt;Revision as of 23:10, 11 March 2008&lt;/td&gt;
			&lt;/tr&gt;
		&lt;/table&gt;</description>
			<pubDate>Tue, 11 Mar 2008 23:10:08 GMT</pubDate>			<dc:creator>Docbook2Wiki</dc:creator>			<comments>http://commons.oreilly.com/wiki/index.php/Talk:Greasemonkey_Hacks/Search</comments>		</item>
		<item>
			<title>Docbook2Wiki: Initial conversion from Docbook</title>
			<link>http://commons.oreilly.com/wiki/index.php?title=Greasemonkey_Hacks/Search&amp;diff=7958&amp;oldid=prev</link>
			<description>&lt;p&gt;Initial conversion from Docbook&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{Greasemonkey Hacks/TOC}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Hacks 47–59: Introduction ==&lt;br /&gt;
&lt;br /&gt;
Search: next to shopping, emailing, and downloading pornography, it's the most popular activity on the Web. With billions of pages in no particular order, search engines have risen to prominence as the gatekeepers of the Internet. Those that survived the dot-com boom and bust are now worth hundreds of billions of dollars, and they probably deserve it. (They certainly deserve it more than their dot-com predecessors. Remember Pets.com? Man, that was a great business model. &amp;quot;Hey, let's sell 50-pound bags of dog food with free shipping.&amp;quot; Brilliant.)&lt;br /&gt;
&lt;br /&gt;
Google is currently the undisputed king of search, so many of the hacks in this chapter focus on Google. But there are hacks here for Yahoo! users too, and a few that work with any search engine.&lt;br /&gt;
&lt;br /&gt;
== Add a Site Search ==&lt;br /&gt;
&lt;br /&gt;
'''Google can restrict your search to a specific site. You can take advantage of this feature to add a site search to every page you visit'''.&lt;br /&gt;
&lt;br /&gt;
There are two ways to restrict Google to return pages on a specific site. The first way is to use the &amp;lt;tt&amp;gt;site:&amp;lt;/tt&amp;gt; keyword in your search results, like this:&lt;br /&gt;
&lt;br /&gt;
 	foo site:example.com&lt;br /&gt;
&lt;br /&gt;
This Google search searches for &amp;lt;tt&amp;gt;foo&amp;lt;/tt&amp;gt; but returns only pages on the ''example.com'' domain. On the search results page, the URL looks like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	http://www.google.com/search?hl=en&amp;amp;&amp;lt;/nowiki&amp;gt;'''q=foo+site%3Aexample.com''' &lt;br /&gt;
          &lt;br /&gt;
&lt;br /&gt;
The second way is to do it in two steps. First, search for foo; then, at the bottom of the search results, click &amp;quot;Search within results.&amp;quot; You will get to a page with another search form, where you can enter &amp;lt;tt&amp;gt;site:example.com&amp;lt;/tt&amp;gt;. The actual search results will be the same as the previous one-step method, but the URL looks different:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	http://www.google.com/search?hl=en&amp;amp;lr=&amp;amp;c2coff=1&amp;amp;&amp;lt;/nowiki&amp;gt;'''q=foo&amp;amp;as_q=site%3Aexample.com'''&lt;br /&gt;
          &lt;br /&gt;
&lt;br /&gt;
This difference is important, because the keyword &amp;lt;tt&amp;gt;foo&amp;lt;/tt&amp;gt; and the site name &amp;lt;tt&amp;gt;site:example.com&amp;lt;/tt&amp;gt; are in separate query parameters. It makes it trivial to reverse-engineer that URL to construct a form that searches a specific site. The form would display a single visible text box named &amp;lt;tt&amp;gt;q&amp;lt;/tt&amp;gt; and also contain a hidden form field named &amp;lt;tt&amp;gt;as_q&amp;lt;/tt&amp;gt; that contains the domain of the current page with a &amp;lt;tt&amp;gt;site:&amp;lt;/tt&amp;gt; prefix.&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
The code is in two parts. The first part creates the site search form and inserts it at the top of the page. The second part styles the form so it is unobtrusive and visually separated from the rest of the page.&lt;br /&gt;
&lt;br /&gt;
This script should run on all pages except pages on ''google.com''. (It would be silly to include a site search on the search results page!)&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''sitesearch.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name			Site Search&lt;br /&gt;
	// @namespace		http://diveintomark.org/projects/greasemonkey/&lt;br /&gt;
	// @description		adds a site search on every page using Google Site Search&lt;br /&gt;
	// @include			http://*&lt;br /&gt;
	// @exclude			http://*.google.tld/*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
	&lt;br /&gt;
	var elmSearchDiv = document.createElement('div');&lt;br /&gt;
	elmSearchDiv.innerHTML =&lt;br /&gt;
		'&amp;lt;form method=&amp;quot;GET&amp;quot; action=&amp;quot;http://www.google.com/search&amp;quot;&amp;gt;' +&lt;br /&gt;
		'&amp;lt;label for=&amp;quot;as_q&amp;quot;&amp;gt;Search this site:&amp;lt;/label&amp;gt; ' + &lt;br /&gt;
		'&amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;as_q&amp;quot; name=&amp;quot;as_q&amp;quot; accesskey=&amp;quot;S&amp;quot;&amp;gt; ' + &lt;br /&gt;
		'&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;q&amp;quot; value=&amp;quot;site:' + location.host + '&amp;quot;&amp;gt;' +&lt;br /&gt;
		'&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Search&amp;quot;&amp;gt;' +&lt;br /&gt;
		'&amp;lt;/form&amp;gt;';&lt;br /&gt;
	document.body.insertBefore(elmSearchDiv, document.body.firstChild);&lt;br /&gt;
	elmSearchDiv.style.fontSize = 'small';&lt;br /&gt;
	elmSearchDiv.style.textAlign = 'right';&lt;br /&gt;
	elmSearchDiv.style.borderBottom = '1px solid silver';&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
After installing the user script from Tools → Install This User Script, go to ''http://www.gnu.org''. You should see a new form in the top-right corner of the page labeled &amp;quot;Search this site:&amp;quot;, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-1|Figure 6-1]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-1&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-1. Site search on ''www.gnu.org'''''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt212.png|Site search on www.gnu.org&lt;br /&gt;
               ]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Enter &amp;lt;tt&amp;gt;gpl compatible&amp;lt;/tt&amp;gt; and click Search. You will be taken to the Google search results showing pages on ''www.gnu.org'' that reference GPL compatibility, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-2|Figure 6-2]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-2&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-2. Site search results for &amp;quot;gpl compatible&amp;quot; on ''www.gnu.org'''''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt213.png|Site search results for &amp;quot;gpl compatible&amp;quot; on www.gnu.org&lt;br /&gt;
               ]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Hacking the Hack ===&lt;br /&gt;
&lt;br /&gt;
Most search engines include functionality to restrict a search to a particular site. If you prefer to use a different search engine, just look at the URLs it uses to do site-specific searches and work your way back to construct the site search form to match.&lt;br /&gt;
&lt;br /&gt;
For example, the relevant query string parameters of a site-specific search on Yahoo! Web Search look like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	http://search.yahoo.com/search?&amp;lt;/nowiki&amp;gt;'''va=gpl+compatible&amp;amp;vs=www.gnu.org'''&lt;br /&gt;
             &lt;br /&gt;
&lt;br /&gt;
The search keywords are in the &amp;lt;tt&amp;gt;va&amp;lt;/tt&amp;gt; parameter, and the domain is in the &amp;lt;tt&amp;gt;vs&amp;lt;/tt&amp;gt; parameter.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;tip&amp;quot;&amp;gt;&lt;br /&gt;
'''Tip'''&lt;br /&gt;
&lt;br /&gt;
There's one difference from Google's site search: the domain to search is specified by itself, without a &amp;lt;tt&amp;gt;site:&amp;lt;/tt&amp;gt; prefix.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To add a site search that uses Yahoo! Web Search, construct the form like so:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	elmSearchDiv.innerHTML =&lt;br /&gt;
		'&amp;lt;form method=&amp;quot;GET&amp;quot; action=&amp;quot;http://search.yahoo.com/search&amp;quot;&amp;gt;' +&lt;br /&gt;
		'&amp;lt;label for=&amp;quot;va&amp;quot;&amp;gt;Search this site:&amp;lt;/label&amp;gt; ' + &lt;br /&gt;
		'&amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;va&amp;quot; name=&amp;quot;va&amp;quot; accesskey=&amp;quot;S&amp;quot;&amp;gt; ' + &lt;br /&gt;
		'&amp;lt;input type=&amp;quot;hidden&amp;quot; name=&amp;quot;vs&amp;quot; value=&amp;quot;' + location.host + '&amp;quot;&amp;gt;' +&lt;br /&gt;
		'&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Search&amp;quot;&amp;gt;' +&lt;br /&gt;
		'&amp;lt;/form&amp;gt;';&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The rest of the script will work unchanged.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;tip&amp;quot;&amp;gt;&lt;br /&gt;
'''Tip'''&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;tt&amp;gt;innerHTML&amp;lt;/tt&amp;gt; property is a great way to inject a complex chunk of HTML into a page. It is not part of the W3C DOM standard, but all modern browsers support it.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want the site search box to appear at the bottom of each page, instead of the top, change this line:&lt;br /&gt;
&lt;br /&gt;
 	document.body.insertBefore(elmSearchDiv, document.body.firstChild);&lt;br /&gt;
&lt;br /&gt;
to this:&lt;br /&gt;
&lt;br /&gt;
 	document.body.appendChild(elmSearchDiv);&lt;br /&gt;
&lt;br /&gt;
You can also alter the styling of the site search form itself. If you want to distinguish it visually from the rest of the page, you could give it a black background with white text. Add these two lines to the end of the user script:&lt;br /&gt;
&lt;br /&gt;
 	elmSearchDiv.style.backgroundColor = 'black';&lt;br /&gt;
 	elmSearchDiv.style.color = 'white';&lt;br /&gt;
&lt;br /&gt;
== Remove Spammy Domains from Search Results ==&lt;br /&gt;
&lt;br /&gt;
'''Fight back against search engine spammers who register domains with multiple &amp;quot;hot&amp;quot; keywords separated by hyphens'''.&lt;br /&gt;
&lt;br /&gt;
Google and other search engines are engaged in an ongoing arms race against spammers, who use every conceivable trick to attain top placement for lucrative search keywords. One such trick is to register a domain name with the keywords themselves, such as ''buy-cheap-prescription-drugs-online.com''. (I just made that up, although I Wouldn't be the slightest bit surprised if it already existed. In fact, I would be surprised if it didn't.) Recently, Google has cracked down on such techniques, but some ''spammy'' domains still show up in search results.&lt;br /&gt;
&lt;br /&gt;
Think of the web sites you visit on a regular basis. I'll bet that none of them contains more than one hyphen. In fact, the only time I ever see multi-hyphen domain names is when a spammer is one step ahead of Google and manages to get his site listed in the results. (I don't buy cheap prescription drugs online, but I did need to refinance my home last year. Search engine results were so overwhelmed with spam, I almost broke down and used a phone book.)&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script removes Google search results where the domain contains more than one hyphen. Once again, the bulk of the logic is contained in the XPath query. This is tricky for two reasons. First, we need to count the number of instances of a particular character in a string, and XPath doesn't have a native function to do that. Second, we need to isolate the entire search result—link, description, everything—and remove it all at once.&lt;br /&gt;
&lt;br /&gt;
We can solve the first problem (counting the hyphens in the domain) by a clever use of the XPath &amp;lt;tt&amp;gt;translate&amp;lt;/tt&amp;gt; function, which &amp;quot;translates&amp;quot; a string by replacing specific characters with other characters. The key here is to tell the &amp;lt;tt&amp;gt;translate&amp;lt;/tt&amp;gt; function to replace a character with nothing (in other words, to remove it altogether). If we munge the URL in a certain way, and the result starts the string &amp;quot;//--&amp;quot;, the original URL must have contained at least two hyphens in its domain. (Many legitimate web publishing systems generate URLs with multiple hyphens in the pathname, so we must be careful not match URLs such as ''http://diveintomark.org/archives/2004/08/13/safari-content-sniffing''.)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;tip&amp;quot;&amp;gt;&lt;br /&gt;
'''Tip'''&lt;br /&gt;
&lt;br /&gt;
A complete list of XPath functions is available at ''http://www.w3schools.com/xpath/xpath_functions.asp''.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We can solve the second problem by using the &amp;lt;tt&amp;gt;ancestor::&amp;lt;/tt&amp;gt; axis. Each search result is wrapped in a &amp;lt;tt&amp;gt;&amp;amp;lt;p class=&amp;quot;g&amp;quot;&amp;gt;&amp;lt;/tt&amp;gt; element. (I have no idea what &amp;lt;tt&amp;gt;g&amp;lt;/tt&amp;gt; stands for. Google likes single-character names; it probably reduces their bandwidth costs.) Once we find a link that contains two hyphens, we can use &amp;lt;tt&amp;gt;&amp;quot;/ancestor::p[@class='g']&amp;quot;&amp;lt;/tt&amp;gt; to get the surrounding paragraph, and then remove the entire search result in one shot.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''hyphenspam.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name			Hyphen Spam Remover&lt;br /&gt;
	// @namespace		http://diveintomark.org/projects/greasemonkey/&lt;br /&gt;
	// @description		remove search results with 2 or more hyphens in domain&lt;br /&gt;
	// @include			http://www.google.com/search*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
&lt;br /&gt;
	var snapFilter = document.evaluate(&lt;br /&gt;
		&amp;quot;//a[starts-with(translate(translate(@href, 'http:', ''), &amp;quot; +&lt;br /&gt;
		&amp;quot;'.:abcdefghijklmnopqrstuvwxyz0123456789', ''), '//--')]&amp;quot; +&lt;br /&gt;
		&amp;quot;/ancestor::p[@class='g']&amp;quot;, document, null,&lt;br /&gt;
		XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);&lt;br /&gt;
	for (var i = snapFilter.snapshotLength - 1; i &amp;gt;= 0; i--) {&lt;br /&gt;
		var elmFilter = snapFilter.snapshotItem(i);&lt;br /&gt;
		elmFilter.parentNode.removeChild(elmFilter);&lt;br /&gt;
	}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
Go to ''http://www.google.com'' and search for &amp;lt;tt&amp;gt;buy cheap lortab site:.ru.&amp;lt;/tt&amp;gt; (The &amp;lt;tt&amp;gt;site:.ru&amp;lt;/tt&amp;gt; finds sites hosted in Russia. I have nothing against Russia per se, except that when I Wrote this, Google seemed to have already cracked down on most spammy domains in ''.com'' and ''.net'', but I found several examples of such domains in ''.ru''.) When you read this, your results will undoubtedly differ, but [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-3|Figure 6-3]] shows what I saw.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-3&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-3. Google search with spammy results'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt220.png|Google search with spammy results]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now, install the script (Tools → Install This User Script), and refresh the Google search results page. My results were the same, except that the script removed the top search result, ''buy-cheap-lortab.on.ufanet.ru'', as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-4|Figure 6-4]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-4&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-4. Google search, now with 10% less spam'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt221.png|Google search, now with 10% less spam]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Hacking the Hack ===&lt;br /&gt;
&lt;br /&gt;
The possibilities here are infinite. Don't want to ever see search results on ''microsoft.com?'' You could alter your search habits to include &amp;lt;tt&amp;gt;-microsoft.com&amp;lt;/tt&amp;gt; in every search. Or you could let Greasemonkey do it for you:&lt;br /&gt;
&lt;br /&gt;
 	var snapFilter = document.evaluate(&lt;br /&gt;
 		&amp;quot;//a[contains('microsoft.com')/ancestor::p[@class='g']&amp;quot;,&lt;br /&gt;
 		document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);&lt;br /&gt;
&lt;br /&gt;
When I'm searching for a specific answer to a technical question, I often find the answer in a mailing list that has been publicly archived and indexed. Here's a variation of this hack that highlights search results that are likely to be part of an archived mailing list, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-5|Figure 6-5]].&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	var snapFilter = document.evaluate(&lt;br /&gt;
		&amp;quot;//a[contains(@href, 'pipermail') or &amp;quot; +&lt;br /&gt;
			&amp;quot;starts-with(@href, 'http://mail') or &amp;quot; +&lt;br /&gt;
			&amp;quot;starts-with(@href, 'http://list')]&amp;quot; +&lt;br /&gt;
		&amp;quot;/ancestor::p[@class='g']&amp;quot;, document, null,&lt;br /&gt;
		XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);&lt;br /&gt;
	for (var i = snapFilter.snapshotLength - 1; i &amp;gt;= 0; i--) {&lt;br /&gt;
		var elmFilter = snapFilter.snapshotItem(i);&lt;br /&gt;
		elmFilter.style.backgroundColor = 'silver';&lt;br /&gt;
	}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-5&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-5. Search results with highlighted mailing list archives'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt224.png|Search results with highlighted mailing list archives]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Find Similar Images ==&lt;br /&gt;
&lt;br /&gt;
'''Explore the Web in a new way by finding other images of the same name'''.&lt;br /&gt;
&lt;br /&gt;
I will be the first to admit that this hack has no practical purpose. I originally conceived it in an IRC channel, when someone posted a link to ''http://images.google.com/images?q=P5170003''. That particular keyword is a filename used by a particular brand of digital camera. Some cameras generate filenames based on the date the photo was taken and a unique identifier within the camera; others simply use an incrementing identifier starting with 1. Many people take digital images and then simply publish them online, without giving the photo a more meaningful filename. The end result is that you can use Google Images to find a random selection of images published by different people. (This particular query finds photos taken on May 17, my wedding anniversary.)&lt;br /&gt;
&lt;br /&gt;
Anyway, this hack converts all unlinked images into links to Google Images to find other random images with the same filename. If that sounds silly, that's because it is. It's also surprisingly fun, if you like that sort of thing.&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script runs on all pages. It uses the &amp;lt;tt&amp;gt;document.images&amp;lt;/tt&amp;gt; collection to find all the images on the page and wraps each of them in a link to ''http://images.google.com/images?q''= plus the image filename. Firefox seriously dislikes replacing an element with another element that contains the original element, so we use the &amp;lt;tt&amp;gt;cloneNode&amp;lt;/tt&amp;gt; method to make a copy of the original &amp;lt;tt&amp;gt;&amp;lt;img&amp;gt;&amp;lt;/tt&amp;gt; element, put it in an &amp;lt;tt&amp;gt;&amp;lt;a&amp;gt;&amp;lt;/tt&amp;gt; element, and then replace the original &amp;lt;tt&amp;gt;&amp;lt;img&amp;gt;&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''similarimages.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name		  Find Similar Images&lt;br /&gt;
	// @namespace	  http://diveintomark.org/projects/greasemonkey/&lt;br /&gt;
	// @description	  links images to find similar images on Google Image Search&lt;br /&gt;
	// @include		  http://*&lt;br /&gt;
	// @exclude		  http://*.google.tld/*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
&lt;br /&gt;
	for (var i = document.images.length - 1; i &amp;gt;= 0; i--) {&lt;br /&gt;
		var elmImage = document.images[i];&lt;br /&gt;
		var usFilename = elmImage.src.split('/').pop();&lt;br /&gt;
		var elmLink = elmImage.parentNode;&lt;br /&gt;
		if (elmLink.nodeName != 'A') {&lt;br /&gt;
			var elmLink = document.createElement('a');&lt;br /&gt;
			elmLink.href = 'http://images.google.com/images?q=' +&lt;br /&gt;
				escape(usFilename);&lt;br /&gt;
			elmLink.title = 'Find images named ' + usFilename;&lt;br /&gt;
			var elmNewImage = elmImage.cloneNode(false);&lt;br /&gt;
			elmLink.appendChild(elmNewImage);&lt;br /&gt;
			elmImage.parentNode.replaceChild(elmLink, elmImage);&lt;br /&gt;
		}&lt;br /&gt;
	}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
After installing the user script (Tools → Install This User Script), visit ''http://randomness.org.uk/photos/index.cgi/months/may_2003''. When you move your cursor over an image, you will see a tool tip displaying the filename of the image, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-6|Figure 6-6]].&lt;br /&gt;
&lt;br /&gt;
Each image on the page is now a link to a Google Images search for images of the same name. This can lead to some pretty random results, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-7|Figure 6-7]].&lt;br /&gt;
&lt;br /&gt;
Have fun exploring accidental cross-sections of the Web!&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-6&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-6. Image tool tips'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt226.png|Image tool tips]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-7&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-7. Other images named P5170003'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt227.png|Other images named P5170003]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Search Wikipedia with Google Site Search ==&lt;br /&gt;
&lt;br /&gt;
'''Replace Wikipedia's slow search engine with Google's lightning-quick site search'''.&lt;br /&gt;
&lt;br /&gt;
I hack because I care. Really. I spend a lot of time on Google, and it shows in the number of hacks I've written that customize my experience of Google's services. The same applies to Wikipedia, the free (and freely licensed) online encyclopedia. I Hold Wikipedia in the highest regard, not only as a useful research tool, but as an example of a successful online community.&lt;br /&gt;
&lt;br /&gt;
So, what's my beef with Wikipedia? Their site search is incredibly slow. I freely admit that I've been spoiled by Google. If I even bother using a site's internal search engine (as opposed to, say, searching Google with the site name as an additional keyword), I am instantly annoyed if the site search doesn't come back with useful results in under one second. Simon Willison shares my frustration, and he wrote this hack that modifies Wikipedia's search form to use Google Site Search instead of the site's internal search engine.&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script runs on all Wikipedia pages. It uses hardcoded knowledge of Wikipedia's page structure to find the search form (&amp;lt;form id=&amp;quot;searchform&amp;quot;&amp;gt;), and then modifies the form's &amp;lt;tt&amp;gt;action&amp;lt;/tt&amp;gt; attribute to point to Google Site Search. The search form has two submit buttons, so the script moves them around and directs one of them to Wikipedia's internal search, while the default button goes to Google Site Search.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''wikipedia-googlesearch.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name		  Search Wikipedia with Google&lt;br /&gt;
	// @namespace	  http://simon.incutio.com/code/greasemonkey/&lt;br /&gt;
	// @description	  Alters Wikipedia search to use Google Site Search&lt;br /&gt;
	// @include		  http://*.wikipedia.org/*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
&lt;br /&gt;
	// based on code by Simon Willison&lt;br /&gt;
	// and included here with his gracious permission&lt;br /&gt;
&lt;br /&gt;
	var form = document.getElementById('searchform');&lt;br /&gt;
	var inputs = form.getElementsByTagName('input');&lt;br /&gt;
	var input = inputs[0];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
	var go = inputs[1];&lt;br /&gt;
	var search = inputs[2];&lt;br /&gt;
	if (form &amp;amp;&amp;amp; input &amp;amp;&amp;amp; go &amp;amp;&amp;amp; search) {&lt;br /&gt;
		// Move Go to the right&lt;br /&gt;
		form.appendChild(go);&lt;br /&gt;
		// Unbold it (by clearing its ID)&lt;br /&gt;
		go.id = '';&lt;br /&gt;
		// Search should be bold instead&lt;br /&gt;
		search.style.fontWeight = 'bold';&lt;br /&gt;
		// Update form to use Google&lt;br /&gt;
		form.action = 'http://www.google.com/search';&lt;br /&gt;
		input.name = 'as_q';&lt;br /&gt;
		// Add hidden q variable for site specific search&lt;br /&gt;
		var q = document.createElement('input');&lt;br /&gt;
		q.type = 'hidden';&lt;br /&gt;
		q.name = 'q';&lt;br /&gt;
		q.value = 'site:' + window.location.host;&lt;br /&gt;
		form.appendChild(q);&lt;br /&gt;
		// Set Go up to behave as normal&lt;br /&gt;
		go.addEventListener('click', function(event) {&lt;br /&gt;
			window.location.href = 'http://en.&amp;lt;/nowiki&amp;gt;GoogleWikipedia searches usingwikipedia.org/wiki/Special' + &lt;br /&gt;
 				':Search?search=' + escape(input.value);&lt;br /&gt;
 			event.preventDefault(); &lt;br /&gt;
 		}, true);&lt;br /&gt;
 	}&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
Before installing the user script, go to ''http://en.wikipedia.org'', enter &amp;lt;tt&amp;gt;logical fallacies&amp;lt;/tt&amp;gt; in the search box on the left, and click Search. The site search will churn and churn, and eventually take you to a page showing search results. Your mileage may vary, but for me, this search takes almost 30 seconds.&lt;br /&gt;
&lt;br /&gt;
Now, install the user script (Tools → Install This User Script), and revisit or refresh ''http://en.wikipedia.org''. The search form looks the same, except that the Go and Search buttons have been reversed. Now, when you type &amp;lt;tt&amp;gt;logical fallacies&amp;lt;/tt&amp;gt; and click Search (or just press Enter), it will take you to Google's site search results of the Wikipedia site, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-8|Figure 6-8]].&lt;br /&gt;
&lt;br /&gt;
If you prefer to use Wikipedia's built-in search engine, you can do so by clicking Go instead of Search. The user script sets up this button to redirect to ''http://en.wikipedia.org/wiki/Special:Search?search=&amp;lt;your_search_keywords&amp;gt;''. If Wikipedia can find an exact match, this will redirect to the result page; otherwise, it will display Wikipedia's search results page.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-8&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-8. Google site search on wikipedia.org'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt229.png|Google site search on wikipedia.org]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Link to Other Search Engines from Google ==&lt;br /&gt;
&lt;br /&gt;
'''Make Google even more useful by adding links to competitors'''.&lt;br /&gt;
&lt;br /&gt;
When Google was young and scrappy (circa 2001), it had an interesting feature. At the bottom of the search results page, Google offered links to try your search on the other major search engines of the day: AltaVista, Hot-bot, Excite, and a few others. The thinking behind it was that maybe you didn't find what you were looking for this time, but you should still try Google first on your next search.&lt;br /&gt;
&lt;br /&gt;
Google is all grown up now, and they are the undisputed king of web search. Somewhere along the way to the top, they quietly dropped this feature. This hack brings it back.&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script runs on Google search result pages. It retrieves the original query from the search form at the top of the page, then constructs a list of links to other search engines and inserts them at the top of the search results.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''tryyoursearchon.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name		   Try Your Search On&lt;br /&gt;
	// @namespace	   http://&amp;lt;/nowiki&amp;gt;Googlelinking to other search enginesdiveintomark.org/projects/greasemonkey/&lt;br /&gt;
 	// @description	   Link to competitors from searchingGoogle&amp;lt;nowiki&amp;gt;Google search results&lt;br /&gt;
	// @include		   http://www.google.tld/search*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
	&lt;br /&gt;
	// based on Butler&lt;br /&gt;
	// http://diveintomark.org/projects/butler/&lt;br /&gt;
	&lt;br /&gt;
	function getOtherWebSearches(q) {&lt;br /&gt;
		q = escape(q);&lt;br /&gt;
		return '' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://search.yahoo.com/search?p=' + q + '&amp;quot;&amp;gt;Yahoo&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://web.ask.com/web?q=' + q + '&amp;quot;&amp;gt;Ask Jeeves&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://alltheweb.com/search?q=' + q + '&amp;quot;&amp;gt;AlltheWeb&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://s.teoma.com/search?q=' + q + '&amp;quot;&amp;gt;Teoma&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://search.msn.com/results.aspx?q=' + q + '&amp;quot;&amp;gt;MSN&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://search.lycos.com/default.asp?query=' + q + '&amp;quot;&amp;gt;Lycos&amp;lt;/a&amp;gt;, '&lt;br /&gt;
	+&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://technorati.com/cosmos/search.html?url=' + q +&lt;br /&gt;
	'&amp;quot;&amp;gt;Technorati&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://feedster.com/search.php?q=' + q + '&amp;quot;&amp;gt;Feedster&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://daypop.com/search?q=' + q + '&amp;quot;&amp;gt;Daypop&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://bloglines.com/search?t=1&amp;amp;amp;amp;q=' + q + '&amp;gt;Bloglines&amp;lt;/a&amp;gt;';&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function addOtherWebSearches() {&lt;br /&gt;
		var elmHeader = document.evaluate(&amp;quot;//table[@bgcolor='#e5ecf9']&amp;quot;,&lt;br /&gt;
			document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,&lt;br /&gt;
			null).singleNodeValue;&lt;br /&gt;
		if (!elmHeader) return;&lt;br /&gt;
		var q = document.forms.namedItem('gs').elements.namedItem('q').value;&lt;br /&gt;
		var elmOther = document.createElement('div');&lt;br /&gt;
		var html = '&amp;lt;p style=&amp;quot;font-size: small&amp;quot;&amp;gt;Try your search on ';&lt;br /&gt;
		html += getOtherWebSearches(q);&lt;br /&gt;
		html += '&amp;lt;/p&amp;gt;';&lt;br /&gt;
		elmOther.innerHTML = html;&lt;br /&gt;
		elmHeader.parentNode.insertBefore(elmOther, elmHeader.nextSibling);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	addOtherWebSearches();&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
After installing the user script (Tools → Install This User Script), go to ''http://www.google.com'' and search for anything. At the top of the search results, you'll see a line with links to execute the same query on other search engines, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-9|Figure 6-9]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-9&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-9. &amp;quot;Try your search on&amp;quot; other search engines'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt231.png|&amp;quot;Try your search on&amp;quot; other search engines]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Hacking the Hack ===&lt;br /&gt;
&lt;br /&gt;
Of course, Google can do more than just search the Web. It also lets you search for images. And of course there are lots of other image search engines. Some specialize in free images, others in commercial images. Some sites, such as Flickr (''http://www.flickr.com'') let you publish your own photos and search photos that other people have published. It sounds like Google Image Search needs a makeover.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''tryyoursearchon2.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name			Try Your Search On&lt;br /&gt;
	// @namespace		http://diveintomark.org/projects/greasemonkey/&lt;br /&gt;
	// @description		Link to competitors from Google web and image search&lt;br /&gt;
	// @include			http://www.google.tld/search*&lt;br /&gt;
	// @include			http://images.google.tld/images*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
	&lt;br /&gt;
	function getOtherWebSearches(q) {&lt;br /&gt;
		q = escape(q);&lt;br /&gt;
		return '' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://search.yahoo.com/search?p=' + q + '&amp;quot;&amp;gt;Yahoo&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://web.ask.com/web?q=' + q + '&amp;quot;&amp;gt;Ask Jeeves&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://alltheweb.com/search?q=' + q + '&amp;quot;&amp;gt;AlltheWeb&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://s.teoma.com/search?q=' + q + '&amp;quot;&amp;gt;Teoma&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://search.msn.com/results.aspx?q=' + q + '&amp;quot;&amp;gt;MSN&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://search.lycos.com/default.asp?query=' + q + '&amp;quot;&amp;gt;Lycos&amp;lt;/a&amp;gt;, '&lt;br /&gt;
	+&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://technorati.com/cosmos/search.html?url=' + q +&lt;br /&gt;
	'&amp;quot;&amp;gt;Technorati&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://feedster.com/search.php?q=' + q + '&amp;quot;&amp;gt;Feedster&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://www.daypop.com/search?q=' + q + '&amp;quot;&amp;gt;Daypop&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://bloglines.com/search?t=1&amp;amp;amp;amp;q=' + q + '&amp;gt;Bloglines&amp;lt;/a&amp;gt;';&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function addOtherWebSearches() {&lt;br /&gt;
		var elmHeader = document.evaluate(&amp;quot;//table[@bgcolor='#e5ecf9']&amp;quot;,&lt;br /&gt;
			document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,&lt;br /&gt;
			null).singleNodeValue;&lt;br /&gt;
		if (!elmHeader) return;&lt;br /&gt;
		var q = document.forms.namedItem('gs').elements.namedItem('q').value;&lt;br /&gt;
		var elmOther = document.createElement('div');&lt;br /&gt;
		var html = '&amp;lt;p style=&amp;quot;font-size: small&amp;quot;&amp;gt;Try your search on ';&lt;br /&gt;
		html += &amp;lt;/nowiki&amp;gt;Googlelinking to other search engines&amp;lt;nowiki&amp;gt;getOtherWebSearches(q);&lt;br /&gt;
		html += '&amp;lt;/p&amp;gt;';&lt;br /&gt;
		elmOther.innerHTML = html;&lt;br /&gt;
		elmHeader.parentNode.insertBefore(elmOther, elmHeader.nextSibling);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function getOtherImageSearches(q) {&lt;br /&gt;
		q = escape(q);&lt;br /&gt;
		return '' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://images.search.yahoo.com/search/images?p=' + q +&lt;br /&gt;
	'&amp;quot;&amp;gt;Yahoo&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://pictures.ask.com/pictures?q=' + q +&lt;br /&gt;
	'&amp;quot;&amp;gt;Ask Jeeves&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://www.alltheweb.com/search?cat=img&amp;amp;q=' + q +&lt;br /&gt;
	'&amp;quot;&amp;gt;AlltheWeb&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://search.msn.com/images/results.aspx?q=' + q + '&amp;quot;&amp;gt;MSN&amp;lt;/a&amp;gt;, '&lt;br /&gt;
	+&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://www.picsearch.com/search.cgi?q=' + q + '&amp;quot;&amp;gt;PicSearch&amp;lt;/a&amp;gt;, '&lt;br /&gt;
	+&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://www.ditto.com/searchResults.asp?ss=' + q + '&amp;quot;&amp;gt;Ditto&amp;lt;/a&amp;gt;, '&lt;br /&gt;
	+&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://www.creatas.com/searchResults.aspx?' + 'searchString=' + q&lt;br /&gt;
	+&lt;br /&gt;
	'&amp;quot;&amp;gt;Creatas&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://www.freefoto.com/search.jsp?queryString=' + q +&lt;br /&gt;
	'&amp;quot;&amp;gt;FreeFoto&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://www.webshots.com/search?query=' + q + '&amp;quot;&amp;gt;WebShots&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://nix.larc.nasa.gov/search?qa=' + q + '&amp;quot;&amp;gt;NASA&amp;lt;/a&amp;gt;, ' +&lt;br /&gt;
	'&amp;lt;a href=&amp;quot;http://www.flickr.com/photos/search/text:' + q + '&amp;quot;&amp;gt;Flickr&amp;lt;/a&amp;gt;';&lt;br /&gt;
		   return s;&lt;br /&gt;
	}&lt;br /&gt;
	function addOtherImageSearches() {&lt;br /&gt;
		var elmTable = document.evaluate( &lt;br /&gt;
			&amp;quot;//a[starts-with(@href, '/images?q=')]/ancestor::table&amp;quot; +			&lt;br /&gt;
&amp;quot;[@width='100%'][@border='0'][@cellpadding='0'][@cellspacing='0']&amp;quot;,&lt;br /&gt;
			document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,&lt;br /&gt;
			null).singleNodeValue;&lt;br /&gt;
		if (!elmTable) { return; }&lt;br /&gt;
		var elmTR = document.createElement('tr');&lt;br /&gt;
		var q = document.forms.namedItem('gs').elements.namedItem('q').value;&lt;br /&gt;
		var html = '&amp;lt;td align=&amp;quot;left&amp;quot;&amp;gt;&amp;lt;span style=&amp;quot;font-size: small&amp;quot;&amp;gt;' +&lt;br /&gt;
			'Try your search on ';&lt;br /&gt;
		html += getOtherImageSearches(q);&lt;br /&gt;
		html += '&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;';&lt;br /&gt;
		elmTR.innerHTML = html;&lt;br /&gt;
&lt;br /&gt;
		elmTable.appendChild(elmTR);&lt;br /&gt;
	}&lt;br /&gt;
	&lt;br /&gt;
	if (/^http:\/\/www\.&amp;lt;/nowiki&amp;gt;searchingGooglegoogle\.[\w\.]+\/search/i.test(location.href)) {&lt;br /&gt;
 		addOtherWebSearches();&lt;br /&gt;
 	}&lt;br /&gt;
 	else if (/^http:\/\/images\.google\.[\w\.]+\/images/i.test(location.href)) {&lt;br /&gt;
 		addOtherImageSearches();&lt;br /&gt;
 	}&lt;br /&gt;
&lt;br /&gt;
After installing the user script (Tools → Install This User Script), go to ''http://images.google.com'' and search for something. At the top of the image results, you'll see a line with links to other image search engines and photo sites, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-10|Figure 6-10]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-10&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-10. &amp;quot;Try your search on&amp;quot; other image search engines'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt233.png|&amp;quot;Try your search on&amp;quot; other image search engines]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Just click the search engine you want to use, and you will jump straight to the search results page for the same keywords.&lt;br /&gt;
&lt;br /&gt;
== Prefetch Yahoo! Search Results ==&lt;br /&gt;
&lt;br /&gt;
'''Automatically prefetch and cache the first search result on Yahoo! Web Search'''.&lt;br /&gt;
&lt;br /&gt;
If you know how to use them properly, search engines are pretty darn good at finding exactly the page you're looking for. Google is so confident in its algorithm that it includes a hidden attribute in the search results page that tells Firefox to prefetch the first search result and cache it. You're probably going to click on the first result anyway, and when you do, it will load almost instantaneously, because your browser has already been there.&lt;br /&gt;
&lt;br /&gt;
Yahoo! Web Search is pretty good, too, but it doesn't yet have this particular feature. So let's add it.&lt;br /&gt;
&lt;br /&gt;
There are two important things about Yahoo! search results that you can discover by viewing the source on its search results page. First, the links of the search results each have a class &amp;lt;tt&amp;gt;yschttl&amp;lt;/tt&amp;gt;. Yahoo! uses this for styling the links with CSS, but you can use it to find the links in the first place. A single XPath query can extract a list of all the links with the class &amp;lt;tt&amp;gt;yschttl&amp;lt;/tt&amp;gt;, and the first one of those is the one we want to prefetch and cache.&lt;br /&gt;
&lt;br /&gt;
The second thing you need to know is that the search results Yahoo! provides are actually redirects through a tracking script on ''rds.yahoo.com'' that records which link you clicked on. A sample link looks like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	http://rds.yahoo.com/S=2766679/K=gpl+compatible/v=2/SID=e/TID=F510_112/ &lt;br /&gt;
	l=WS1/R=2/IPC=us/SHE=0/H=1/SIG=11sgv1lum/EXP=1116517280/*-http%3A//www.gnu. &lt;br /&gt;
	org/licenses/gpl-faq.html&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
To save time and bandwidth, and to avoid skewing Yahoo!'s tracking statistics, this user script will extract the target URL out of the first search result link before requesting it. The target URL is always at the end of the tracking URL, after the *-, with characters such as colons (:) escaped into their hexadecimal equivalents. Here's the target URL in the previous example:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	http://www.gnu.org/licenses/gpl-faq.html&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When I say &amp;quot;prefetch and cache,&amp;quot; there is really only one step: prefetch. By default, Firefox automatically caches pages according to HTTP's caching directives and your browser preferences. For this script to have the desired effect, make sure that your browser preferences are set to enable caching pages. Open a new window or tab, go to ''about:config'', and double-check the following preferences:&lt;br /&gt;
&lt;br /&gt;
 	* browser.cache.disk.enable			 /* should be &amp;quot;true&amp;quot; */&lt;br /&gt;
 	* browser.cache.check_doc_frequency  /* should be 0, 2, or 3 */&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;tip&amp;quot;&amp;gt;&lt;br /&gt;
'''Tip'''&lt;br /&gt;
&lt;br /&gt;
about:config shows you all your browser preferences, even ones that are not normally configurable through the Options dialog. Type part of a preference name (such as &amp;lt;tt&amp;gt;browser.cache&amp;lt;/tt&amp;gt;) in the Filter box to narrow the list of displayed preferences.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script will run on Yahoo! search results pages. It works by finding the first search result on the page and retrieving it. You might think that it would be easier to add a &amp;lt;tt&amp;gt;&amp;lt;link rel=&amp;quot;prefetch&amp;quot;&amp;gt;&amp;lt;/tt&amp;gt; to the page, which is how Google's prefetching works. Unfortunately, this does not work, because by the time the user script executes, Firefox has already prefetched all the links it's going to fetch for the page.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''yahooprefetch.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name			Yahoo! Prefetcher&lt;br /&gt;
	// @namespace		http://diveintomark.org/projects/greasemonkey/&lt;br /&gt;
	// @description		prefetch first link on Yahoo! web search results&lt;br /&gt;
	// @include			http://search.yahoo.com/search*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
&lt;br /&gt;
	var elmFirstResult = document.evaluate(&amp;quot;//a[@class='yschttl']&amp;quot;, document,&lt;br /&gt;
		null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;&lt;br /&gt;
	if (!elmFirstResult) return;&lt;br /&gt;
	var urlFirstResult = unescape(elmFirstResult.href.replace(/^.*\*-/, ''));&lt;br /&gt;
	var oRequest = {&lt;br /&gt;
		method: 'GET',&lt;br /&gt;
		url: urlFirstResult,&lt;br /&gt;
		headers: {'X-Moz': 'prefetch',&lt;br /&gt;
				  'Referer': document.location.href}};&lt;br /&gt;
		GM_log('&amp;lt;/nowiki&amp;gt;searchingprefetching Yahoo search resultsprefetching ' + urlFirstResult);&lt;br /&gt;
 		GM_xmlhttpRequest(oRequest);&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
To verify that the script is working properly, you'll need to clear your browser cache. You don't need to do this every time, just once to prove to yourself that the script is doing something. To clear your cache, go to the Tools menu and select Options; then, go to the Privacy tab and click the Clear button next to Cache.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;tip&amp;quot;&amp;gt;&lt;br /&gt;
'''Tip'''&lt;br /&gt;
&lt;br /&gt;
You can also use the LiveHTTPHeaders extension to see exactly which URLs Firefox fetches. You can download the extension at ''http://livehttpheaders.mozdev.org/''.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now, install the user script from Tools → Install This User Script, and then go to ''http://search.yahoo.com'' and search for &amp;lt;tt&amp;gt;gpl compatible&amp;lt;/tt&amp;gt;. The prefetching happens in the background after the page is fully loaded, so wait a second or two after the search results come up. There won't be any visible indication onscreen that Firefox is prefetching the link. You might see some additional activity on your modem or network card, but it's hard to separate this from the activity of loading the rest of the Yahoo! search results page.&lt;br /&gt;
&lt;br /&gt;
Open a new browser window or tab and go to ''about:cache''. This displays information about Firefox's browser cache. Under &amp;quot;Disk cache device,&amp;quot; click List Cache Entries. You should see a key for ''http://www.gnu.org/philosophy/license-list.html''. This is the result of Firefox prefetching and caching the first Yahoo! search results. Click that URL to see specific information about the cache entry, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-11|Figure 6-11]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-11&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-11. Information about a prefetched page'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt238.png|Information about a prefetched page]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Hacking the Hack ===&lt;br /&gt;
&lt;br /&gt;
By now, you should realize that this prefetching technique can be used anywhere, with any links. Do you use some other search engine, perhaps a site-specific search engine such as Microsoft Developer's Network (MSDN)? You can apply the same technique to those search results.&lt;br /&gt;
&lt;br /&gt;
For example, going to ''http://msdn.microsoft.com'' and searching for &amp;lt;tt&amp;gt;active accessibility&amp;lt;/tt&amp;gt; takes you to a search results page at this URL:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	http://search.microsoft.com/search/results.&lt;br /&gt;
	aspx?&amp;lt;/nowiki&amp;gt;'''qu=active+accessibility'''&amp;amp;View=msdn&amp;amp;st=b&amp;amp;c=0&amp;amp;s=1&amp;amp;swc=0&lt;br /&gt;
&lt;br /&gt;
If you view source on the page, you will see that the result links are contained within a &amp;lt;tt&amp;gt;&amp;amp;lt;div class=&amp;quot;results&amp;quot;&amp;gt;&amp;lt;/tt&amp;gt; tag. This means that the first result can be found with this XPath query:&lt;br /&gt;
&lt;br /&gt;
 	  var elmFirstResult = document.evaluate(&amp;quot;//div[@class='results']//&lt;br /&gt;
 	a[@href]&amp;quot;,&lt;br /&gt;
 		document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).&lt;br /&gt;
 	singleNodeValue;&lt;br /&gt;
&lt;br /&gt;
Unlike with Yahoo! search results, search result links are not redirected through a tracking script, so you will need to change this line:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	  var urlFirstResult = unescape(elmFirstResult.href.replace(/^.*\*-/, ''));&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to this:&lt;br /&gt;
&lt;br /&gt;
 	  var urlFirstResult = elmFirstResult.href;&lt;br /&gt;
&lt;br /&gt;
The rest of the script will work unchanged.&lt;br /&gt;
&lt;br /&gt;
== Browse the Web Through Google's Cache ==&lt;br /&gt;
&lt;br /&gt;
'''Change links in cached pages to point to the cached version'''.&lt;br /&gt;
&lt;br /&gt;
One of the nicest (and most controversial) features of Google's web search is its ability to show you a cached version of the page. This is useful if the original server is temporarily down or is just horrendously slow. It is also useful to see if the web publisher is playing tricks on Google to try to increase their search ranking, since the cache will show you the page that the site returned when Google's bots came a-crawling. The only downside of the Google cache is that links in the cached page point to the original site (which might still be unavailable, which was the reason you had to look at the cached version in the first place).&lt;br /&gt;
&lt;br /&gt;
This hack modifies the cached pages that Google displays and adds links within the cached page to also point to Google's cache of the linked page.&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script runs on Google cache pages. Google uses a variety of raw IP addresses to display cached pages, so we match on any IP address or domain name and simply look at the structure of the URL path and query parameters to determine whether we're looking at a cached page. If this causes false positives for you, you can exclude specific domains with an &amp;lt;tt&amp;gt;@exclude&amp;lt;/tt&amp;gt; parameter.&lt;br /&gt;
&lt;br /&gt;
There is one important thing to note in this code. Normally, I would use the &amp;lt;tt&amp;gt;document.links&amp;lt;/tt&amp;gt; collection to get a list of all the links on the page. However, &amp;lt;tt&amp;gt;document.links&amp;lt;/tt&amp;gt; is a dynamic collection. If you add a link to the page while iterating through the collection, you could end up in an infinite loop. Therefore, I use the &amp;lt;tt&amp;gt;document.evaluate&amp;lt;/tt&amp;gt; function to return a static snapshot of all the links on the page. See &amp;quot;Master XPath Expressions&amp;quot; &amp;lt;tt&amp;gt;[[Greasemonkey Hacks/Getting Started#Master XPath Expressions|'''[Hack #8]''']]&amp;lt;/tt&amp;gt; for more information about static snapshots.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''google.cache.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name		   Google Cache Continue&lt;br /&gt;
	// @namespace	   http://babylon.idlevice.co.uk/javascript/greasemonkey/&lt;br /&gt;
	// @description	   Convert Google cache links to also use Google cache&lt;br /&gt;
	// @include		   http://*/search?*q=cache:*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
&lt;br /&gt;
	// based on code by Jonathon Ramsey&lt;br /&gt;
	// and included here with his gracious permission&lt;br /&gt;
&lt;br /&gt;
	/* Modify these vars to change the appearance of the cache links */&lt;br /&gt;
	var cacheLinkText = 'cache';&lt;br /&gt;
	var cacheLinkStyle = &amp;quot;\&lt;br /&gt;
		a.googleCache {\&lt;br /&gt;
&lt;br /&gt;
			font:normal bold x-small sans-serif;\&lt;br /&gt;
			color:red;\&lt;br /&gt;
			background-color:yellow;\&lt;br /&gt;
			padding:0 0.6ex 0.4ex 0.3ex;\&lt;br /&gt;
			margin:0.3ex;\&lt;br /&gt;
		}\&lt;br /&gt;
		a.&amp;lt;/nowiki&amp;gt;searchingGoogle cached pagesgoogleCache:hover {\&lt;br /&gt;
 			color:yellow;\&lt;br /&gt;
 			background-color:red;\&lt;br /&gt;
 		}\&lt;br /&gt;
 		p#googleCacheExplanation {\&lt;br /&gt;
 			border:1px solid green;\&lt;br /&gt;
 			padding:1ex 0.5ex;\&lt;br /&gt;
 			font-family:sans-serif;\&lt;br /&gt;
 		}&amp;quot;;&lt;br /&gt;
 &lt;br /&gt;
 	addStyles(cacheLinkStyle);&lt;br /&gt;
 &lt;br /&gt;
 	if (googleHasNoCache()) {&lt;br /&gt;
 		addUncachedLink(urlPage); &lt;br /&gt;
 		return;&lt;br /&gt;
 		}&lt;br /&gt;
 &lt;br /&gt;
 		var arParts = window.location.href.match(/http:\/\/[^\/]*\/([^\+]*)(\&lt;br /&gt;
 		+[^&amp;amp;]*)/);&lt;br /&gt;
 		var urlPage = arParts[1];&lt;br /&gt;
 		var sTerms = arParts[2];&lt;br /&gt;
 &lt;br /&gt;
 		var bAlter = false;&lt;br /&gt;
 		var snapLinks = document.evaluate('//a[@href]', document,&lt;br /&gt;
 			null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);&lt;br /&gt;
 		for (var i = 0; i &amp;lt; snapLinks.snapshotLength; i++) {&lt;br /&gt;
 			var elmLink = snapLinks.snapshotItem(i);&lt;br /&gt;
 			if (bAlter &amp;amp;&amp;amp; linkIsHttp(elmLink)) {&lt;br /&gt;
 				addCacheLink(elmLink, sTerms, cacheLinkText);&lt;br /&gt;
 			}&lt;br /&gt;
 			if (isLastGoogleLink(elmLink)) {&lt;br /&gt;
 				bAlter = true;&lt;br /&gt;
 				addExplanation(elmLink, cacheLinkText);&lt;br /&gt;
 			}&lt;br /&gt;
 		}&lt;br /&gt;
 		function addStyles(cacheLinkStyle) {&lt;br /&gt;
 			var style = document.createElement('style');&lt;br /&gt;
 			style.type = 'text/css'; &lt;br /&gt;
 			style.innerHTML = cacheLinkStyle;&lt;br /&gt;
 			document.body.appendChild(style);&lt;br /&gt;
 		}&lt;br /&gt;
 		function googleHasNoCache() {&lt;br /&gt;
 			return 0 == document.title.indexOf('Google Search: cache:'); &lt;br /&gt;
 		}&lt;br /&gt;
 		function addUncachedLink(url) {&lt;br /&gt;
 &lt;br /&gt;
 			var urlUncached = url.split('cache:')[1];&lt;br /&gt;
 			var elmP = document.createElement('p');&lt;br /&gt;
 			elmP.id = 'searchingGoogle cached pages&amp;lt;nowiki&amp;gt;googleCacheExplanation';&lt;br /&gt;
			elmP.innerHTML = &amp;quot;&amp;lt;b&amp;gt;Uncached:&amp;lt;/b&amp;gt; &amp;lt;a href='http://&amp;quot; + urlUncached +&lt;br /&gt;
				&amp;quot;'&amp;gt;&amp;quot; + urlUncached + '&amp;lt;/a&amp;gt;';&lt;br /&gt;
			var suggestions = document.getElementsByTagName('blockquote')[0];&lt;br /&gt;
			document.body.replaceChild(elmP,&lt;br /&gt;
				suggestions.previousSibling.previousSibling); &lt;br /&gt;
		}&lt;br /&gt;
		function linkIsHttp(link) {&lt;br /&gt;
			return 0 == link.href.search(/^http/); &lt;br /&gt;
		}&lt;br /&gt;
		function isLastGoogleLink(elmLink) {&lt;br /&gt;
			return (-1 &amp;lt; elmLink.text.indexOf('cached text'));&lt;br /&gt;
		}&lt;br /&gt;
		function addExplanation(link, cacheLinkText) { &lt;br /&gt;
			var p = document.createElement('p');&lt;br /&gt;
			p.id = 'googleCacheExplanation'; &lt;br /&gt;
			p.innerHTML = &amp;quot;Use &amp;lt;a href='&amp;quot; +&lt;br /&gt;
				document.location.href +&lt;br /&gt;
				&amp;quot;' class='googleCache'&amp;gt;&amp;quot; +&lt;br /&gt;
				cacheLinkText +&lt;br /&gt;
				&amp;quot;&amp;lt;/a&amp;gt; links &amp;lt;/nowiki&amp;gt;Googledefaulting to cached pages&amp;lt;nowiki&amp;gt;to continue using the Google cache.&amp;lt;/a&amp;gt;&amp;quot;;&lt;br /&gt;
			var tableCell = link.parentNode.parentNode.parentNode.parentNode;&lt;br /&gt;
			tableCell.appendChild(p); &lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		function addCacheLink(elmLink, sTerms, cacheLinkText) {&lt;br /&gt;
			var cacheLink = document.createElement('a');&lt;br /&gt;
			cacheLink.href = getCacheLinkHref(elmLink, sTerms);&lt;br /&gt;
			cacheLink.appendChild(document.createTextNode(cacheLinkText));&lt;br /&gt;
			cacheLink.className = 'googleCache';&lt;br /&gt;
			elmLink.parentNode.insertBefore(cacheLink, elmLink.nextSibling);&lt;br /&gt;
		}&lt;br /&gt;
		function getCacheLinkHref(elmLink, sTerms) {&lt;br /&gt;
			var href = elmLink.href.replace(/^http:\/\//, '');&lt;br /&gt;
			var fragment = '';&lt;br /&gt;
			if (hrefLinksToFragment(href)) {&lt;br /&gt;
				var arParts = href.match(/([^#]*)#(.*)/, href);&lt;br /&gt;
				href = arParts[1];&lt;br /&gt;
				fragment = '#' + arParts[2];&lt;br /&gt;
			}&lt;br /&gt;
			return 'http://www.google.com/search?q=cache:' + href + sTerms + fragment; &lt;br /&gt;
		}&lt;br /&gt;
		&lt;br /&gt;
		function hrefLinksToFragment(href) {&lt;br /&gt;
			return (-1 &amp;lt; href.indexOf('#')); &lt;br /&gt;
		}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
After installing the user script (Tools → Install This User Script), go to ''http://www.google.com'' and search for &amp;lt;tt&amp;gt;&amp;quot;xml on the web&amp;quot;&amp;lt;/tt&amp;gt; (including the quotes). At the time of this writing, the first result is for my article on O'Reilly's XML.com, titled &amp;quot;XML on the Web Has Failed,&amp;quot; at ''http://www.xml.com/pub/a/2004/07/21/dive.html''. Click the Cached link next to the first search result to see Google's cache of this article, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-12|Figure 6-12]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-12&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-12. Cached copy of &amp;quot;XML on the Web Has Failed&amp;quot;'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt244.png|Cached copy of &amp;quot;XML on the Web Has Failed&amp;quot;]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each link in the article has been augmented with a &amp;quot;cache&amp;quot; link. Click the &amp;quot;cache&amp;quot; link next to the &amp;quot;Dive into XML&amp;quot; image, and it will take you to the cached copy of all the XML.com articles I've written, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-13|Figure 6-13]].&lt;br /&gt;
&lt;br /&gt;
Google does not keep cached copies of every page on the Internet. If a page is moved or deleted, it will eventually disappear from Google's cache. Or the publisher might use a &amp;lt;tt&amp;gt;&amp;lt;meta&amp;gt;&amp;lt;/tt&amp;gt; element to tell Google not to cache a specific page. If you try to follow a link to a page that is not in Google's cache, Google will display an empty search results page informing you that the cached page could not be found, and the script will insert a link to the original page.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-13&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-13. Cached copy of &amp;quot;Dive into XML&amp;quot; articles'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt245.png|Cached copy of &amp;quot;Dive into XML&amp;quot; articles]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Add More Book Reviews to Google Print ==&lt;br /&gt;
&lt;br /&gt;
'''Link to other book review and shopping sites after reading Google's book excerpts'''.&lt;br /&gt;
&lt;br /&gt;
Google Print is a wonderful service, offering a fully searchable index into books before you buy them. But when you're ready to buy, there are a limited number of choices of online bookstores to which Google provides direct links.&lt;br /&gt;
&lt;br /&gt;
Of course, there's more on the Web than just shopping sites. All Consuming (''http://www.allconsuming.net'') specializes in aggregating third-party reviews of books that people have recently blogged. This hack adds a link from Google Print to the page on All Consuming that displays reviews of the book you're interested in.&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script runs on Google Print pages. It finds the ISBN of the book you're currently viewing and adds links to the pages on All Consuming just below the link to Froogle.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''otherbookreviews.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name			Other Book Reviews&lt;br /&gt;
	// @namespace		http://diveintomark.org/projects/greasemonkey/&lt;br /&gt;
	// @description		add links to AllConsuming.net in &amp;lt;/nowiki&amp;gt;GoogleGoogle Print&amp;lt;nowiki&amp;gt;Google Print&lt;br /&gt;
	// @include			http://print.google.com/print*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
&lt;br /&gt;
	// based on Butler&lt;br /&gt;
	// http://diveintomark.org/projects/butler/&lt;br /&gt;
	&lt;br /&gt;
	var elmFroogle = document.evaluate( &amp;quot;//a[contains(@href, 'froogle')]&amp;quot;,&lt;br /&gt;
		document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,&lt;br /&gt;
		null).singleNodeValue;&lt;br /&gt;
	if (!elmFroogle) return;&lt;br /&gt;
	var sISBN = unescape(elmFroogle.href).split('q=')[1].split('&amp;amp;')[0];&lt;br /&gt;
&lt;br /&gt;
	var elmAllConsuming = document.createElement(&amp;quot;a&amp;quot;);&lt;br /&gt;
	elmAllConsuming.href = 'http://allconsuming.net/item/asin/' + sISBN;&lt;br /&gt;
	elmAllConsuming.style.display = &amp;quot;block&amp;quot;;&lt;br /&gt;
	elmAllConsuming.innerHTML = &amp;quot;&amp;lt;br&amp;gt;Reviews @&amp;lt;br&amp;gt;AllConsuming.net&amp;quot;;&lt;br /&gt;
	elmFroogle.parentNode.insertBefore(elmAllConsuming,&lt;br /&gt;
		elmFroogle.nextSibling);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
After installing the user script (Tools → Install This User Script), go to ''http://print.google.com'' and search for &amp;lt;tt&amp;gt;Romeo and Juliet&amp;lt;/tt&amp;gt;. Click the link under Book Results titled &amp;quot;Romeo and Juliet by William Shakespeare.&amp;quot; In the list on the left, you will see a new link to All Consuming, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-14|Figure 6-14]].&lt;br /&gt;
&lt;br /&gt;
=== Hacking the Hack ===&lt;br /&gt;
&lt;br /&gt;
Of course, there are other online bookstores and book services, too. ISBN.nu is an independent site that tracks book prices at several different online shopping sites. To add an additional link to look up the current book on ISBN.nu, add the following lines to the end of the script:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	var elmISBNnu = document.createElement(&amp;quot;a&amp;quot;);&lt;br /&gt;
	elmISBNnu.href = 'http://isbn.nu/' + sISBN;&lt;br /&gt;
	elmISBNnu.style.display = &amp;quot;block&amp;quot;;&lt;br /&gt;
	elmISBNnu.appendChild(document.createTextNode(&amp;quot;ISBN.nu&amp;quot;));&lt;br /&gt;
	elmFroogle.parentNode.insertBefore(elmISBNnu, elmFroogle.nextSibling);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-14&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-14. AllConsuming.net link on Google Print'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt248.png|AllConsuming.net link on Google Print]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Autocomplete Search Terms as You Type ==&lt;br /&gt;
&lt;br /&gt;
'''Google can suggest your search terms before you even finish typing them'''.&lt;br /&gt;
&lt;br /&gt;
It's true: Google is clairvoyant. It can guess what you're going to search for even before you've typed it. Well, maybe that overstates it. But it can certainly take an educated guess, based on the popularity and number of results of certain keywords.&lt;br /&gt;
&lt;br /&gt;
Don't believe me? Visit ''http://www.google.com/webhp?complete=1'' and start typing, and Google will autocomplete your query after you've typed just a few characters. This is insanely cool, and virtually nobody knows about it. And even people &amp;quot;in the know&amp;quot; need to visit a special page to use it. This hack makes this functionality work everywhere—even on the Google home page (''http://www.google.com'').&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script runs on all Google pages, but it works only on pages with a search form. Of course, being Google, this is most pages, including the home page and web search result pages.&lt;br /&gt;
&lt;br /&gt;
This hack doesn't do any of the autocompletion work itself. It relies entirely on Google's own functionality for suggesting completions for partial search terms, defined entirely in ''http://www.google.com/ac.js''. All we need to do is create a &amp;lt;tt&amp;gt;&amp;lt;script&amp;gt;&amp;lt;/tt&amp;gt; element pointing to Google's own code, and insert it into the page. Then, we tell Google to activate it by adding another &amp;lt;tt&amp;gt;&amp;lt;script&amp;gt;&amp;lt;/tt&amp;gt; element that calls Google's own &amp;lt;tt&amp;gt;InstallAC&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''google-autocomplete.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name		 Google Autocomplete&lt;br /&gt;
	// @namespace    http://diveintomark.org/projects/greasemonkey/&lt;br /&gt;
	// @description  Autocomplete search keywords as you type&lt;br /&gt;
	// @include      http://*.google.tld/*&lt;br /&gt;
	// @exclude      http://*/*complete=1*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
&lt;br /&gt;
	function getSearchBox(sFormName) {&lt;br /&gt;
		return document.forms.namedItem(sFormName);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function injectAC(sFormName) {&lt;br /&gt;
		var elmScript = document.createElement('script');&lt;br /&gt;
		elmScript.src = 'http://www.google.com/ac.js';&lt;br /&gt;
		document.body.appendChild(elmScript);&lt;br /&gt;
		var elmDriver = document.createElement('script');&lt;br /&gt;
		elmDriver.innerHTML = 'var elmForm = document.forms.namedItem(&amp;quot;' +&lt;br /&gt;
		sFormName + '&amp;quot;);\n' +&lt;br /&gt;
		'InstallAC(elmForm, elmForm.elements.namedItem(&amp;quot;q&amp;quot;),' +&lt;br /&gt;
		'elmForm.elements.namedItem(&amp;quot;btnG&amp;quot;), &amp;quot;search&amp;quot;, &amp;quot;en&amp;quot;);';&lt;br /&gt;
		document.body.appendChild(elmDriver);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	var sFormName = 'f';&lt;br /&gt;
	var elmForm = getSearchBox(sFormName);&lt;br /&gt;
	if (!elmForm) {&lt;br /&gt;
		sFormName = 'gs';&lt;br /&gt;
		elmForm = getSearchBox(sFormName);&lt;br /&gt;
	}&lt;br /&gt;
	if (!elmForm) { return; }&lt;br /&gt;
	window.setTimeout(function() { injectAC(sFormName); }, 100);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
After installing the user script (Tools → Install This User Script), go to ''http://www.google.com'' and start typing the word &amp;lt;tt&amp;gt;greasemonkey&amp;lt;/tt&amp;gt;. After typing the first three letters, &amp;lt;tt&amp;gt;gre&amp;lt;/tt&amp;gt;, you will see a drop-down menu with possible completions, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-15|Figure 6-15]].&lt;br /&gt;
&lt;br /&gt;
If you continue typing &amp;lt;tt&amp;gt;greasemonkey&amp;lt;/tt&amp;gt; and then type a space, Google will suggest possible multiword searches, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-16|Figure 6-16]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-15&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-15. Autocompletion of &amp;quot;gre&amp;quot; search'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt250.png|Autocompletion of &amp;quot;gre&amp;quot; search]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-16&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-16. Suggestions for multiword &amp;quot;greasemonkey&amp;quot; search'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt251.png|Suggestions for multiword &amp;quot;greasemonkey&amp;quot; search]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Highlight Search Terms ==&lt;br /&gt;
&lt;br /&gt;
'''When you click through to a page from a search engine, highlight the terms you originally searched for'''.&lt;br /&gt;
&lt;br /&gt;
Have you ever searched for something on Google, then clicked through to a page and been unable to figure out why this page ranked so highly? Not only does it seem irrelevant, you can't even find the keywords you originally searched for! This hack tracks your search engine clickthroughs and highlights your original search keywords when you leave the results page for a given hit.&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script runs on all pages except Google search result pages. The code is divided into three parts:&lt;br /&gt;
&lt;br /&gt;
# The &amp;lt;tt&amp;gt;highlightWord&amp;lt;/tt&amp;gt; function walks the DOM tree recursively and calls itself with each node, and then checks whether the current node is a block of text that contains a specific search term. If so, it wraps the word in a &amp;lt;tt&amp;gt;span&amp;lt;/tt&amp;gt; tag and styles it with CSS to display with a yellow background.&lt;br /&gt;
# The &amp;lt;tt&amp;gt;highlightSearchKeywords&amp;lt;/tt&amp;gt; function looks at the page you came from (&amp;lt;tt&amp;gt;document.referrer&amp;lt;/tt&amp;gt;). If you came from a search results page, it parses out the keywords you originally searched for and calls &amp;lt;tt&amp;gt;highlightWord&amp;lt;/tt&amp;gt; with each keyword.&lt;br /&gt;
# Finally, we add an event listener that calls &amp;lt;tt&amp;gt;highlightSearchKeywords&amp;lt;/tt&amp;gt; after the page has completed loading.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''searchhi.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name		 Search Highlight&lt;br /&gt;
	// @namespace    http://www.kryogenix.org/code/&lt;br /&gt;
	// @description  highlight &amp;lt;/nowiki&amp;gt;Googlehighlighting search terms in resulting pages&amp;lt;nowiki&amp;gt;search terms when coming a search engine&lt;br /&gt;
	// @include *&lt;br /&gt;
	// @exclude      http://www.google.tld/search*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
&lt;br /&gt;
	// based on code by Stuart Langridge&lt;br /&gt;
	// and included here with his gracious permission&lt;br /&gt;
	// http://www.kryogenix.org/code/browser/searchhi/&lt;br /&gt;
&lt;br /&gt;
	function highlightWord(node, word) {&lt;br /&gt;
		if (node.hasChildNodes) {&lt;br /&gt;
			for (var hi_cn = 0; hi_cn&amp;lt;node.childNodes.length; hi_cn++) {&lt;br /&gt;
				highlightWord(node.childNodes[hi_cn], word);&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		if (node.nodeType == Node.TEXT_NODE) {&lt;br /&gt;
			var tempNodeVal, tempWordVal, pn, nv, ni, before, docWordVal,&lt;br /&gt;
				after, hiwordtext, hiword;&lt;br /&gt;
			tempNodeVal = node.nodeValue.toLowerCase();&lt;br /&gt;
			tempWordVal = word.toLowerCase();&lt;br /&gt;
			if (tempNodeVal.indexOf(tempWordVal) != -1) {&lt;br /&gt;
				pn = node.parentNode;&lt;br /&gt;
				if (pn.className != &amp;quot;searchword&amp;quot;) {&lt;br /&gt;
				nv = node.nodeValue;&lt;br /&gt;
				ni = tempNodeVal.indexOf(tempWordVal);&lt;br /&gt;
				before = document.createTextNode(nv.substr(0,ni));&lt;br /&gt;
				docWordVal = nv.substr(ni, word.length);&lt;br /&gt;
				after = document.createTextNode(nv.substr(ni+word.length));&lt;br /&gt;
				hiwordtext = document.createTextNode(docWordVal);&lt;br /&gt;
				hiword = document.createElement(&amp;quot;span&amp;quot;);&lt;br /&gt;
				hiword.className = &amp;quot;searchword&amp;quot;;&lt;br /&gt;
				hiword.style.backgroundColor = 'yellow';&lt;br /&gt;
				&lt;br /&gt;
				hiword.style.color = 'black';&lt;br /&gt;
				hiword.appendChild(hiwordtext);&lt;br /&gt;
				pn.insertBefore(before, node);&lt;br /&gt;
				pn.insertBefore(hiword, node);&lt;br /&gt;
				pn.insertBefore(after, node);&lt;br /&gt;
				pn.removeChild(node);&lt;br /&gt;
				}&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function &amp;lt;/nowiki&amp;gt;Googlehighlighting search terms in resulting pageshighlightSearchKeywords() {&lt;br /&gt;
 		var ref = document.referrer;&lt;br /&gt;
 		if (ref.indexOf('?') == -1) { return; }&lt;br /&gt;
 		var qs = ref.substr(ref.indexOf('?')+1);&lt;br /&gt;
 		var qsa = qs.split('&amp;amp;');&lt;br /&gt;
 		for (var i = 0; i &amp;lt; qsa.length; i++) {&lt;br /&gt;
 			var qsip = qsa[i].split('=');&lt;br /&gt;
 			if (qsip.length == 1) { continue; }&lt;br /&gt;
 			if (qsip[0] == 'q') {&lt;br /&gt;
 				var words = unescape(qsip[1].replace(/\+/g,' ')).split(/\s+/);&lt;br /&gt;
 				for (var w = words.length - 1; w &amp;gt;= 0; w--) {&lt;br /&gt;
 				highlightWord(document.body, words[w]);&lt;br /&gt;
 				}&lt;br /&gt;
 			}&lt;br /&gt;
 		}&lt;br /&gt;
 	}&lt;br /&gt;
 &lt;br /&gt;
 	window.addEventListener('load', highlightSearchKeywords, true);&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
After installing the user script (Tools → Install This User Script), go to ''http://www.google.com'' and search for &amp;lt;tt&amp;gt;greasemonkey&amp;lt;/tt&amp;gt;. Click through to the Greasemonkey home page (''http://greasemonkey.mozdev.org/''), and you will see the word ''Greasemonkey'' highlighted in several places, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-17|Figure 6-17]].&lt;br /&gt;
&lt;br /&gt;
The script can also handle multiword searches. Go to ''http://www.google.com'' and search for &amp;lt;tt&amp;gt;download firefox&amp;lt;/tt&amp;gt;. Click through to the Firefox home page (''http://www.mozilla.org''), and you will see both ''download'' and ''firefox'' highlighted in several places, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-18|Figure 6-18]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;tip&amp;quot;&amp;gt;&lt;br /&gt;
'''Tip'''&lt;br /&gt;
&lt;br /&gt;
The large &amp;quot;Get Firefox&amp;quot; banner near the top is not highlighted because this text is actually an image.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-17&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-17. Greasemonkey home page with &amp;quot;Greasemonkey&amp;quot; highlighted'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt253.png|Greasemonkey home page with &amp;quot;Greasemonkey&amp;quot; highlighted]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-18&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-18. Firefox home page with &amp;quot;download&amp;quot; and &amp;quot;firefox&amp;quot; highlighted'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt254.png|Firefox home page with &amp;quot;download&amp;quot; and &amp;quot;firefox&amp;quot; highlighted]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Hacking the Hack ===&lt;br /&gt;
&lt;br /&gt;
It's easy to extend this script to handle search engines other than Google. Whenever you click from one page to another, you have access to the referring page in &amp;lt;tt&amp;gt;document.referrer&amp;lt;/tt&amp;gt;. (That's why the script works in the first place.) Yahoo! Web Search uses a slightly different URL on its result pages. On Google, your search keywords are stored in the &amp;lt;tt&amp;gt;q&amp;lt;/tt&amp;gt; parameter; on Yahoo!, they are stored in the p parameter. To highlight search terms when coming from either Google or Yahoo!, change this line:&lt;br /&gt;
&lt;br /&gt;
 	if (qsip[0] == 'q') {&lt;br /&gt;
&lt;br /&gt;
to this:&lt;br /&gt;
&lt;br /&gt;
 	if (qsip[0] == 'q' || qsip[0] == 'p') {&lt;br /&gt;
&lt;br /&gt;
You might also want to exclude Yahoo! search result pages by adding this line to the script's metadata section:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// @exclude http://search.yahoo.com/*&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This prevents the script from highlighting your search terms on the second page of Yahoo!'s search results.&lt;br /&gt;
&lt;br /&gt;
== Remember Recent Google Searches ==&lt;br /&gt;
&lt;br /&gt;
'''Track what you search for and which search results you follow'''.&lt;br /&gt;
&lt;br /&gt;
Google recently added yet another beta service: My Search History (''http://www.google.com/searchhistory/''). In a nutshell, you log into your Google account, and My Search History remembers which keywords you search for and which search results you end up following. A nice idea, but it has some limitations that disappointed me when I tried it. My Search History isn't immediately available on the Google home page. Also, clicking a previous search simply reexecutes the search, instead of actually taking me to the result I followed last time. How is that useful? I remember what I searched for; what I want to know is what I found!&lt;br /&gt;
&lt;br /&gt;
This hack lets me do what I had hoped the &amp;quot;My Search History&amp;quot; tool would do.&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script runs on all Google pages. The code itself breaks down into three distinct parts:&lt;br /&gt;
&lt;br /&gt;
# The &amp;lt;tt&amp;gt;SavedSearches&amp;lt;/tt&amp;gt; function and associated prototype methods are used to create a persistent array—i.e., an &amp;lt;tt&amp;gt;Array&amp;lt;/tt&amp;gt; class that saves its data to the Firefox preferences database.&lt;br /&gt;
# The &amp;lt;tt&amp;gt;getCurrentSearchText&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;addCurrentSearch&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;clearSavedSearches&amp;lt;/tt&amp;gt;, and &amp;lt;tt&amp;gt;injectRecentSearches&amp;lt;/tt&amp;gt; functions handle the basic operations of the script. Whenever you execute a Google search, the script adds your keywords to its persistent array, and then alters the search results page to include a list of your recent searches.&lt;br /&gt;
# The &amp;lt;tt&amp;gt;trackClick&amp;lt;/tt&amp;gt; function is where the real magic happens. On search result pages, we register &amp;lt;tt&amp;gt;trackClick&amp;lt;/tt&amp;gt; as a global &amp;lt;tt&amp;gt;onclick&amp;lt;/tt&amp;gt; event handler. When you click on anything on the search results page, &amp;lt;tt&amp;gt;trackClick&amp;lt;/tt&amp;gt; is called. It looks at where you clicked, and if you clicked on a search result, it stores the title and URL of the link before following it.&lt;br /&gt;
&lt;br /&gt;
The end result is seamless: you search, click a search result, and visit the result page. But invisibly, behind the scenes, the user script has tracked and stored your every movement.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;warning&amp;quot;&amp;gt;&lt;br /&gt;
'''Warning'''&lt;br /&gt;
&lt;br /&gt;
As this hack demonstrates, user scripts have the potential to track virtually everything you do on the Web. This includes what you search for, where you go, how long you stay, and even the passwords you enter on secure sites. Combine this with the ability of user scripts to send data to any site at any time, with the &amp;lt;tt&amp;gt;GM_xmlhttpRequest&amp;lt;/tt&amp;gt; function, and you have a recipe for catastrophic privacy violations.&lt;br /&gt;
&lt;br /&gt;
None of the hacks in this book compromise your privacy in any way. For example, this script only stores data in your local Firefox preference database, and you can clear it at any time. But you need to be aware that third-party user scripts can do a great deal of damage. Install only scripts that you personally understand, or from sources you trust. This is good advice for any type of download.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''recentsearches.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name         Recent Searches&lt;br /&gt;
	// @namespace	 http://diveintomark.org/projects/greasemonkey/&lt;br /&gt;
	// @description  remember and display recent Google searches&lt;br /&gt;
	// @include		 http://www.google.com/*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
&lt;br /&gt;
	function SavedSearches() {&lt;br /&gt;
		var iCount = GM_getValue('count') || 0;&lt;br /&gt;
		for (var i = 0; i &amp;lt; iCount; i++) {&lt;br /&gt;
			this.push({&lt;br /&gt;
				&amp;quot;searchtext&amp;quot;: GM_getValue('searchtext.' + i, ''),&lt;br /&gt;
				&amp;quot;searchresult&amp;quot;: GM_getValue('searchresult.' + i, '')});&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
	SavedSearches.prototype = new Array();&lt;br /&gt;
&lt;br /&gt;
	SavedSearches.prototype.find = function(sSearchText) {&lt;br /&gt;
		for (var i = this.length - 1; i &amp;gt;= 0; i--) {&lt;br /&gt;
			if (this[i] == sSearchText) {&lt;br /&gt;
				return i;&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
		return -1;&lt;br /&gt;
	};&lt;br /&gt;
&lt;br /&gt;
	SavedSearches.prototype.append = function(sSearchText) {&lt;br /&gt;
		GM_setValue('searchtext.' + this.length, sSearchText);&lt;br /&gt;
		this.push({&amp;quot;searchtext&amp;quot;: sSearchText});&lt;br /&gt;
		GM_setValue('count', this.length);&lt;br /&gt;
	};&lt;br /&gt;
&lt;br /&gt;
	var arSavedSearches = new SavedSearches();&lt;br /&gt;
&lt;br /&gt;
	function getCurrentSearchText() {&lt;br /&gt;
		var elmForm = document.forms.namedItem('gs');&lt;br /&gt;
		if (!elmForm) { return; }&lt;br /&gt;
		var elmSearchBox = elmForm.elements.namedItem('q');&lt;br /&gt;
		if (!elmSearchBox) { return; }&lt;br /&gt;
		var sKeyword = elmSearchBox.value;	&lt;br /&gt;
		if (!sKeyword) { return; }&lt;br /&gt;
		return sKeyword;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function addCurrentSearch() {&lt;br /&gt;
		var sCurrentSearchText = getCurrentSearchText();&lt;br /&gt;
		if (!sCurrentSearchText) { return; }&lt;br /&gt;
		var sLastSearch = null;&lt;br /&gt;
		if (arSavedSearches.length) {&lt;br /&gt;
			sLastSearch = arSavedSearches[arSavedSearches.length - 1];&lt;br /&gt;
		}&lt;br /&gt;
		if (sLastSearch &amp;amp;&amp;amp;&lt;br /&gt;
			(sLastSearch['searchtext'] == sCurrentSearchText)) {&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
		arSavedSearches.append(sCurrentSearchText);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function clearSavedSearches() {&lt;br /&gt;
		for (var i = 0; i &amp;lt; arSavedSearches.length; i++) {&lt;br /&gt;
			GM_setValue('searchtext.' + i, '');&lt;br /&gt;
			GM_setValue('searchresult.' + i, '');&lt;br /&gt;
		}&lt;br /&gt;
		GM_setValue('count', 0);&lt;br /&gt;
		arSavedSearches = new SavedSearches();&lt;br /&gt;
		var &amp;lt;/nowiki&amp;gt;Googleremembering recent searches&amp;lt;nowiki&amp;gt;elmRecentSearches = document.getElementById('recentsearcheslist');&lt;br /&gt;
		if (elmRecentSearches) {&lt;br /&gt;
			elmRecentSearches.innerHTML = '';&lt;br /&gt;
		}			&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function injectRecentSearches() {&lt;br /&gt;
		if (!arSavedSearches.length) { return; }&lt;br /&gt;
		var elmFirst = document.evaluate(&amp;quot;//table[@bgcolor='#e5ecf9']&amp;quot;,&lt;br /&gt;
			document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,&lt;br /&gt;
			null).singleNodeValue;&lt;br /&gt;
		if (!elmFirst) {&lt;br /&gt;
			elmFirst = document.evaluate(&amp;quot;//form[@name='f']&amp;quot;,&lt;br /&gt;
				document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,&lt;br /&gt;
				null).singleNodeValue;&lt;br /&gt;
		}&lt;br /&gt;
		if (!elmFirst) { return; }&lt;br /&gt;
		var htmlRecentSearches = '&amp;lt;p style=&amp;quot;font-size: small&amp;quot;&amp;gt;&amp;lt;/nowiki&amp;gt;Googleremembering recent searchesRecent searches:&lt;br /&gt;
 ';&lt;br /&gt;
 		var iDisplayedCount = 0;&lt;br /&gt;
 		for (var i = arSavedSearches.length - 1;&lt;br /&gt;
 			(iDisplayedCount &amp;lt;10) &amp;amp;&amp;amp; (i &amp;gt;= 0); i--) {&lt;br /&gt;
 			var oSearch = arSavedSearches[i];&lt;br /&gt;
 			if (!oSearch['searchresult']) { continue; }&lt;br /&gt;
 			var sSearchResult = oSearch['searchresult'];&lt;br /&gt;
 			var iSpacePos = sSearchResult.indexOf(' ');&lt;br /&gt;
 			var sHref = sSearchResult.substring(0, iSpacePos);&lt;br /&gt;
 			var sTitle = sSearchResult.substring(iSpacePos + 1);&lt;br /&gt;
 			htmlRecentSearches += '&amp;lt;a href=&amp;quot;' + sHref + '&amp;quot; title=&amp;quot;' +&lt;br /&gt;
 				sTitle + '&amp;quot;&amp;gt;' + oSearch['searchtext'] + '&amp;lt;/a&amp;gt; &amp;amp;amp;middot; ';&lt;br /&gt;
 			iDisplayedCount++;&lt;br /&gt;
 		}&lt;br /&gt;
 		if (!iDisplayedCount) { return; }&lt;br /&gt;
 		htmlRecentSearches += '[&amp;lt;a id=&amp;quot;clearsavedsearches&amp;quot; ' +&lt;br /&gt;
 			'title=&amp;quot;Clear saved searches&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;clear&amp;lt;/a&amp;gt;]&amp;amp;lt;/p&amp;gt;';&lt;br /&gt;
 		var elmWrapper = document.createElement('div');&lt;br /&gt;
 		elmWrapper.id = &amp;quot;recentsearcheslist&amp;quot;;&lt;br /&gt;
 		elmWrapper.innerHTML = htmlRecentSearches;&lt;br /&gt;
 		elmFirst.parentNode.insertBefore(elmWrapper, elmFirst.nextSibling);&lt;br /&gt;
 		window.addEventListener('load', function() {&lt;br /&gt;
 			var elmClearLink = document.getElementById('clearsavedsearches');&lt;br /&gt;
 			elmClearLink.addEventListener('click', clearSavedSearches, true);&lt;br /&gt;
 		}, true);&lt;br /&gt;
 	}&lt;br /&gt;
 &lt;br /&gt;
 	function trackClick(event) {&lt;br /&gt;
 		var sHref, sTitle;&lt;br /&gt;
 		var elmTarget = event.target;&lt;br /&gt;
 		while ((elmTarget.nodeName != 'A') &amp;amp;&amp;amp;&lt;br /&gt;
 				(elmTarget.nodeName != 'BODY')) {&lt;br /&gt;
 			elmTarget = elmTarget.parentNode;&lt;br /&gt;
 		}&lt;br /&gt;
 		if (elmTarget.nodeName != 'A') { return; }&lt;br /&gt;
 		var elmParent = elmTarget.parentNode;&lt;br /&gt;
 		while ((elmParent.nodeName != 'P') &amp;amp;&amp;amp;&lt;br /&gt;
 			(elmParent.nodeName != 'BODY')) {&lt;br /&gt;
 			elmParent = elmParent.parentNode;&lt;br /&gt;
 		}&lt;br /&gt;
 		if (elmParent.nodeName != 'P') { return; }&lt;br /&gt;
 		if (elmParent.getAttribute('class') != 'g') { return; }	&lt;br /&gt;
 		sHref = elmTarget.href;&lt;br /&gt;
 		sTitle = elmTarget.textContent;&lt;br /&gt;
 		var iSearchIndex = arSavedSearches.find(getCurrentSearchText());&lt;br /&gt;
 		if (iSearchIndex == -1) {&lt;br /&gt;
 			addCurrentSearch();&lt;br /&gt;
 			iSearchIndex = arSavedSearches.length - 1;&lt;br /&gt;
 		}&lt;br /&gt;
 		GM_setValue('searchresult.' + iSearchIndex,&lt;br /&gt;
 				sHref + ' ' + sTitle);&lt;br /&gt;
 	}&lt;br /&gt;
 &lt;br /&gt;
 	if (/^\/search/.test(location.pathname)) {&lt;br /&gt;
 		injectRecentSearches();&lt;br /&gt;
 		addCurrentSearch();	&lt;br /&gt;
 		document.addEventListener('click', trackClick, true);&lt;br /&gt;
 	} else if (/^\/$/.test(location.pathname)) {&lt;br /&gt;
 		injectRecentSearches();&lt;br /&gt;
 	}			&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
After installing the user script (Tools → Install This User Script), go to ''http://www.google.com'' and search for something. Click on an interesting search result. Lather, rinse, and repeat. Each time you revisit the Google home page, you will see a growing list of your recent searches, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-19|Figure 6-19]]. Clicking on a recent search term will take you to the same link you followed when you originally executed the search.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-19&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-19. Recent searches on the Google home page'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt259.png|Recent searches on the Google home page]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You will also see the list of recent searches on the search results page itself. Hovering over a recent search displays the title of the linked page, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-20|Figure 6-20]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-20&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-20. Recent searches on Google's search results page'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt260.png|Recent searches on Google's search results page]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Hacking the Hack ===&lt;br /&gt;
&lt;br /&gt;
The links in the recent searches list go directly to the search result you clicked when you searched. But what if you want to rerun the search and go somewhere else? That's easy enough. In the &amp;lt;tt&amp;gt;injectRecentSearches&amp;lt;/tt&amp;gt; function, find these two lines:&lt;br /&gt;
&lt;br /&gt;
 	var sHref = sSearchResult.substring(0, iSpacePos);&lt;br /&gt;
 	var sTitle = sSearchResult.substring(iSpacePos + 1);&lt;br /&gt;
&lt;br /&gt;
And change them like this:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	var sHref = 'http://www.google.com/search?q=' + escape(sSearchResult); &lt;br /&gt;
	var sTitle = 'previously found ' + sSearchResult.substring(iSpacePos + 1) + &lt;br /&gt;
		'\n' + sSearchResult.substring(0, iSpacePos);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now, if you hover over a link in the recent searches list, the tool tip will display the title and URL of the page you went to last time. If you click the link, it will reexecute the search so that you can choose a different search result this time.&lt;br /&gt;
&lt;br /&gt;
== Add Keyboard Shortcuts to Google Search Results ==&lt;br /&gt;
&lt;br /&gt;
'''If you search frequently and type as quickly as you think, you'll appreciate this keyboard-only hack'''.&lt;br /&gt;
&lt;br /&gt;
I love Google. I use Google 50 times a day…literally. I actually used Google once to look up my own phone number. I was placing an order over the phone when the customer service representative asked me for my home phone, and I totally drew a blank. Has that ever happened to you? I should really get one of those weblog thingies all the kids are talking about, so I can regurgitate personal anecdotes like this in a virtual medium, instead of wasting all this paper. But I digress.&lt;br /&gt;
&lt;br /&gt;
As I was saying, I search a lot, and I type very quickly. And if I'm looking for very specific things, and Google is so very good at finding them, I usually find what I'm looking for in the first page of search results, which means that this hack is perfect for me, because it numbers the search results and lets me follow them without moving my hands off the keyboard.&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script runs on all Google pages. It uses an XPath query to find all the search results (they're each wrapped in a &amp;lt;tt&amp;gt;&amp;amp;lt;p class=&amp;quot;g&amp;quot;&amp;gt;&amp;lt;/tt&amp;gt; element), and then inserts red numbers beside each search result, from 1 to 9, then 0 for the 10th result. The last line of the script ties it all together by registering a global &amp;lt;tt&amp;gt;onkeydown&amp;lt;/tt&amp;gt; event handler that checks whether you typed a number, and if so, finds the associated search result and follows the link.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''google-searchkeys.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name			Google Searchkeys&lt;br /&gt;
	// @namespace		http://www.imperialviolet.org&lt;br /&gt;
	// @description		Adds one-press access keys to Google search results&lt;br /&gt;
	// @include			http://www.google.*/search*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
	&lt;br /&gt;
	// based on code by Adam Langley&lt;br /&gt;
	// and included here with his gracious permission&lt;br /&gt;
	&lt;br /&gt;
	var results = document.evaluate(&amp;quot;//p[@class='g']&amp;quot;, document, null,&lt;br /&gt;
		XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);&lt;br /&gt;
	var counter = 1;&lt;br /&gt;
	var querybox = document.evaluate(&amp;quot;//input[@name='q']&amp;quot;, document, null,&lt;br /&gt;
		XPathResult.ORDERED_NODE_ITERATOR_TYPE, null).iterateNext();&lt;br /&gt;
	var next_nodes = document.evaluate(&lt;br /&gt;
		&amp;quot;//a[span[@class='b' and text()='Next']]&amp;quot;,&lt;br /&gt;
		document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);&lt;br /&gt;
	var prev_nodes = document.evaluate(&lt;br /&gt;
		&amp;quot;//a[span[@class='b' and text()='Previous']]&amp;quot;,&lt;br /&gt;
		document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);&lt;br /&gt;
	var nextlink = null;&lt;br /&gt;
	var prevlink = null;&lt;br /&gt;
	if (next_nodes.snapshotLength) {&lt;br /&gt;
		nextlink = next_nodes.snapshotItem(0).getAttribute('href');&lt;br /&gt;
	}&lt;br /&gt;
	if (prev_nodes.snapshotLength) {&lt;br /&gt;
		prevlink = prev_nodes.snapshotItem(0).getAttribute('href');&lt;br /&gt;
	}&lt;br /&gt;
	prev_nodes = next_nodes = null;&lt;br /&gt;
	var links = new Array();&lt;br /&gt;
	for (var i = 0; i &amp;lt; results.snapshotLength; ++i) {&lt;br /&gt;
		var result = results.snapshotItem(i);&lt;br /&gt;
		links.push(result.firstChild.nextSibling.getAttribute(&amp;quot;href&amp;quot;));&lt;br /&gt;
		var newspan = document.createElement(&amp;quot;span&amp;quot;);&lt;br /&gt;
		newspan.setAttribute(&amp;quot;style&amp;quot;, &amp;quot;color:red; font-variant: small-caps;&amp;quot;);&lt;br /&gt;
		newspan.appendChild(document.createTextNode('' + counter++ + ' '));&lt;br /&gt;
		result.insertBefore(newspan, result.firstChild);&lt;br /&gt;
	}&lt;br /&gt;
	results = null;&lt;br /&gt;
&lt;br /&gt;
	function keypress_handler(e) {&lt;br /&gt;
		if (e.ctrlKey || e.altKey || e.metaKey) { return true; }&lt;br /&gt;
		if (e.target.nodeName == 'INPUT' &amp;amp;&amp;amp; e.target.name == 'q') {&lt;br /&gt;
			return true;&lt;br /&gt;
		}&lt;br /&gt;
		var keypressed = String.fromCharCode(e.which);&lt;br /&gt;
		if (nextlink &amp;amp;&amp;amp; (keypressed == 'l' || keypressed == 'L' ||&lt;br /&gt;
				 keypressed == '.')) {&lt;br /&gt;
			if (e.shiftKey) {&lt;br /&gt;
				window.open(nextlink,'Search Results','');&lt;br /&gt;
			} else {&lt;br /&gt;
				document.location.href = nextlink;&lt;br /&gt;
			}&lt;br /&gt;
			return false;&lt;br /&gt;
		}&lt;br /&gt;
		if (prevlink &amp;amp;&amp;amp; (keypressed == 'h' || keypressed == 'H' ||&lt;br /&gt;
				 keypressed == ',')) {&lt;br /&gt;
			if (e.shiftKey) {&lt;br /&gt;
				window.open(prevlink,'Search Results','');&lt;br /&gt;
			} else {&lt;br /&gt;
				document.location.href = prevlink;&lt;br /&gt;
			}&lt;br /&gt;
			return false;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		if (keypressed &amp;lt;'0' || keypressed &amp;gt; '9') {&lt;br /&gt;
			return true;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		var resnum = e.which - &amp;quot;0&amp;quot;.charCodeAt(0);&lt;br /&gt;
		if (resnum == 0) {&lt;br /&gt;
			resnum = 10;&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		if (e.shiftKey) {&lt;br /&gt;
			window.open(links[resnum - 1],'Search Results','');&lt;br /&gt;
		} else {&lt;br /&gt;
			document.location.href = links[resnum - 1];&lt;br /&gt;
		}&lt;br /&gt;
		return false;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	document.addEventListener('keydown', keypress_handler, false);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
After installing the user script (Tools → Install This User Script), go to ''http://www.google.com'' and search for something, such as &amp;lt;tt&amp;gt;ipod&amp;lt;/tt&amp;gt;. Next to each Google search result, you will see a red number, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-21|Figure 6-21]]. Typing that number will redirect you to that linked search result.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-21&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-21. Search results with keyboard shortcuts'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt264.png|Search results with keyboard shortcuts]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Hacking the Hack ===&lt;br /&gt;
&lt;br /&gt;
Besides numbering the search results, this script has some hidden features. On a search results page, type L to see the next 10 search results, or type H to go back to the previous 10 search results.&lt;br /&gt;
&lt;br /&gt;
In addition, you can hold the Shift key while typing a number to open the search result in a new window. This also works for jumping between previous and next pages of search results: Shift-L opens the next 10 results in a new window, and Shift-H opens the previous 10 results in a new window.&lt;br /&gt;
&lt;br /&gt;
== Use Recent Searches and Google SearchKeys Together ==&lt;br /&gt;
&lt;br /&gt;
'''Recent Searches needs an update to play nicely with Google SearchKeys'''.&lt;br /&gt;
&lt;br /&gt;
I stumbled onto this hack by accident. I had been running Recent Searches [[Greasemonkey Hacks/Search#Remember Recent Google Searches|'''[Hack #57]''']] for a few weeks, and I heard about Google SearchKeys [[Greasemonkey Hacks/Search#Add Keyboard Shortcuts to Google Search Results|'''[Hack #58]''']] on the Greasemonkey mailing list. I went to install it and immediately fell in love with it, but after a few searches, I realized that my recent searches list wasn't being updated anymore.&lt;br /&gt;
&lt;br /&gt;
After investigating, I discovered that, because of the way the Google SearchKeys user script works, it was never calling the &amp;lt;tt&amp;gt;onclick&amp;lt;/tt&amp;gt; handler I Had defined for the search result links. Instead, Google SearchKeys simply parsed out the URL of each search result link and assigned it to &amp;lt;tt&amp;gt;window.location.href&amp;lt;/tt&amp;gt;, thus loading the result page and creating the illusion of &amp;quot;following&amp;quot; the link. The illusion was almost perfect, except that my Recent Searches script was assuming that the only way to follow a result link was to click it (or navigate to it with the keyboard and press Enter, but either way would trigger the link's &amp;lt;tt&amp;gt;onclick&amp;lt;/tt&amp;gt; handler).&lt;br /&gt;
&lt;br /&gt;
After a little intensive research, I had a solution so ingenious that my editor agreed it was worthy of its own hack. JavaScript has a feature called ''watchpoints''. On every object, you can set a watchpoint on one of the object's properties. When that property is about to be changed, the JavaScript engine will call a callback function of your choosing, with the property name, the old value, and the new value.&lt;br /&gt;
&lt;br /&gt;
By setting watchpoints on &amp;lt;tt&amp;gt;document.location, document.location.href, window. location&amp;lt;/tt&amp;gt;, and &amp;lt;tt&amp;gt;window.location.href&amp;lt;/tt&amp;gt;, we can notice when a script (such as Google SearchKeys) is trying to move to a different page programmatically. We can save the URL of the new page to our ''recent changes'' database and then let the script go about its merry way.&lt;br /&gt;
&lt;br /&gt;
=== The Code ===&lt;br /&gt;
&lt;br /&gt;
This user script runs on all Google pages, because it displays the list of recent searches on the Google home page as well as on search results pages. The bulk of this script is the same as &amp;quot;Remember Recent Google Searches&amp;quot; [[Greasemonkey Hacks/Search#Remember Recent Google Searches|'''[Hack #57]''']]; the changes are listed in boldface.&lt;br /&gt;
&lt;br /&gt;
Save the following user script as ''recentsearches2.user.js'':&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&lt;br /&gt;
	// ==UserScript==&lt;br /&gt;
	// @name			Recent Searches&lt;br /&gt;
	// @namespace		http://diveintomark.org/projects/greasemonkey/&lt;br /&gt;
	// @description		remember and display recent Google searches&lt;br /&gt;
	// @include			http://www.google.*/search*&lt;br /&gt;
	// ==/UserScript==&lt;br /&gt;
	&lt;br /&gt;
	// based on code by Adam Langley&lt;br /&gt;
	// and included here with his gracious permission&lt;br /&gt;
	// http://www.imperialviolet.org/page24.html&lt;br /&gt;
&lt;br /&gt;
	function SavedSearches() {&lt;br /&gt;
		var iCount = GM_getValue('count') || 0;&lt;br /&gt;
		for (var i = 0; i &amp;lt;iCount; i++) {&lt;br /&gt;
			this.push({&lt;br /&gt;
				&amp;quot;searchtext&amp;quot;: GM_getValue('searchtext.' + i, ''),&lt;br /&gt;
				&amp;quot;searchresult&amp;quot;: GM_getValue('searchresult.' + i, '')});&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	SavedSearches.prototype = new Array();&lt;br /&gt;
&lt;br /&gt;
	SavedSearches.prototype.find = function(sSearchText) {&lt;br /&gt;
		for (var i = this.length - 1; i &amp;gt;= 0; i--) {&lt;br /&gt;
			if (this[i] == sSearchText) {&lt;br /&gt;
				return i;&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
		return -1;&lt;br /&gt;
	};&lt;br /&gt;
&lt;br /&gt;
	SavedSearches.prototype.append = function(sSearchText) {&lt;br /&gt;
		GM_setValue('searchtext.' + this.length, sSearchText);&lt;br /&gt;
		this.push({&amp;quot;searchtext&amp;quot;: sSearchText});&lt;br /&gt;
		GM_setValue('count', this.length);&lt;br /&gt;
	};&lt;br /&gt;
&lt;br /&gt;
	var arSavedSearches = new SavedSearches();&lt;br /&gt;
&lt;br /&gt;
	function getCurrentSearchText() {&lt;br /&gt;
		var elmForm = document.forms.namedItem('gs');&lt;br /&gt;
		if (!elmForm) { return; }&lt;br /&gt;
		var elmSearchBox = elmForm.elements.namedItem('q');&lt;br /&gt;
		if (!elmSearchBox) { return; }&lt;br /&gt;
		var sKeyword = elmSearchBox.value;&lt;br /&gt;
		if (!sKeyword) { return; }&lt;br /&gt;
		return sKeyword;&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function addCurrentSearch() {&lt;br /&gt;
		var sCurrentSearchText = getCurrentSearchText();&lt;br /&gt;
		if (!sCurrentSearchText) { return; }&lt;br /&gt;
		var sLastSearch = null;&lt;br /&gt;
		if (arSavedSearches.length) {&lt;br /&gt;
			sLastSearch = arSavedSearches[arSavedSearches.length - 1];&lt;br /&gt;
		}&lt;br /&gt;
		if (sLastSearch &amp;amp;&amp;amp;&lt;br /&gt;
			(sLastSearch['searchtext'] == sCurrentSearchText)) {&lt;br /&gt;
			return;&lt;br /&gt;
		}&lt;br /&gt;
		arSavedSearches.append(sCurrentSearchText);&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function clearSavedSearches() {&lt;br /&gt;
		for (var i = 0; i &amp;lt;arSavedSearches.length; i++) {&lt;br /&gt;
			GM_setValue('searchtext.' + i, '');&lt;br /&gt;
			GM_setValue('searchresult.' + i, '');&lt;br /&gt;
		}&lt;br /&gt;
		GM_setValue('count', 0);&lt;br /&gt;
		arSavedSearches = new SavedSearches();&lt;br /&gt;
		var elmRecentSearches = document.getElementById('recentsearcheslist');&lt;br /&gt;
		if (elmRecentSearches) {&lt;br /&gt;
			elmRecentSearches.innerHTML = '';&lt;br /&gt;
		}&lt;br /&gt;
	}&lt;br /&gt;
&lt;br /&gt;
	function injectRecentSearches() {&lt;br /&gt;
		if (!arSavedSearches.length) { return; }&lt;br /&gt;
		var elmFirst = document.evaluate(&amp;quot;//table[@bgcolor='#e5ecf9']&amp;quot;,&lt;br /&gt;
			document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,&lt;br /&gt;
			null).singleNodeValue;&lt;br /&gt;
		if (!elmFirst) {&lt;br /&gt;
			elmFirst = document.evaluate(&amp;quot;//form[@name='f']&amp;quot;,&lt;br /&gt;
				document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,&lt;br /&gt;
				null).singleNodeValue;&lt;br /&gt;
			}&lt;br /&gt;
			if (!elmFirst) { return; }&lt;br /&gt;
			var htmlRecentSearches = '&amp;lt;p style=&amp;quot;font-size: small&amp;quot;&amp;gt;&amp;lt;/nowiki&amp;gt;Googlerecent searches and Google SearchKeysRecent searches:&lt;br /&gt;
 ';&lt;br /&gt;
 			var iDisplayedCount = 0;&lt;br /&gt;
 			for (var i = arSavedSearches.length - 1;&lt;br /&gt;
 				(iDisplayedCount &amp;lt; 10) &amp;amp;&amp;amp; (i &amp;gt;= 0); i--) {&lt;br /&gt;
 				var oSearch = arSavedSearches[i];&lt;br /&gt;
 				if (!oSearch['searchresult']) { continue; }&lt;br /&gt;
 				var sSearchResult = oSearch['searchresult'];&lt;br /&gt;
 				var iSpacePos = sSearchResult.indexOf(' ');&lt;br /&gt;
 				var sHref = sSearchResult.substring(0, iSpacePos);&lt;br /&gt;
 				var sTitle = sSearchResult.substring(iSpacePos + 1);&lt;br /&gt;
 				Googlerecent searches and Google SearchKeyshtmlRecentSearches += '&amp;lt;a href=&amp;quot;' + sHref + '&amp;quot; title=&amp;quot;' +&lt;br /&gt;
 				sTitle + '&amp;quot;&amp;gt;' + oSearch['searchtext'] + '&amp;lt;/a&amp;gt; &amp;amp;amp;middot; ';&lt;br /&gt;
 				iDisplayedCount++;&lt;br /&gt;
 			}&lt;br /&gt;
 			if (!iDisplayedCount) { return; }&lt;br /&gt;
 			htmlRecentSearches += '[&amp;lt;a id=&amp;quot;clearsavedsearches&amp;quot; ' +&lt;br /&gt;
 				'title=&amp;quot;Clear saved searches&amp;quot; href=&amp;quot;#&amp;quot;&amp;gt;clear&amp;lt;/a&amp;gt;]&amp;amp;lt;/p&amp;gt;';&lt;br /&gt;
 			var elmWrapper = document.createElement('div');&lt;br /&gt;
 			elmWrapper.id = &amp;quot;recentsearcheslist&amp;quot;;&lt;br /&gt;
 			elmWrapper.innerHTML = htmlRecentSearches;&lt;br /&gt;
 			elmFirst.parentNode.insertBefore(elmWrapper, elmFirst.nextSibling);&lt;br /&gt;
 			window.addEventListener('load', function() {&lt;br /&gt;
 				var elmClearLink = document.getElementById('clearsavedsearches');&lt;br /&gt;
 				elmClearLink.addEventListener('click', clearSavedSearches, true);&lt;br /&gt;
 			}, true);&lt;br /&gt;
 		}&lt;br /&gt;
 &lt;br /&gt;
 		function trackClick(event) {&lt;br /&gt;
 			var sHref, sTitle;&lt;br /&gt;
 			'''if (typeof(event) == 'string') {'''&lt;br /&gt;
 '''				sHref = event;'''&lt;br /&gt;
 '''&amp;lt;nowiki&amp;gt;				sTitle = '';&amp;lt;/nowiki&amp;gt;'''&lt;br /&gt;
 '''			} else {'''&lt;br /&gt;
 				var elmTarget = event.target;&lt;br /&gt;
 				while ((elmTarget.nodeName != 'A') &amp;amp;&amp;amp;&lt;br /&gt;
 				(elmTarget.nodeName != 'BODY')) {&lt;br /&gt;
 				elmTarget = elmTarget.parentNode;&lt;br /&gt;
 				}&lt;br /&gt;
 				if (elmTarget.nodeName != 'A') { return; }&lt;br /&gt;
 				var elmParent = elmTarget.parentNode;&lt;br /&gt;
 				while ((elmParent.nodeName != 'P') &amp;amp;&amp;amp;&lt;br /&gt;
 				(elmParent.nodeName != 'BODY')) {&lt;br /&gt;
 				elmParent = elmParent.parentNode;&lt;br /&gt;
 				}&lt;br /&gt;
 				if (elmParent.nodeName != 'P') { return; }&lt;br /&gt;
 				if (elmParent.getAttribute('class') != 'g') { return; }&lt;br /&gt;
 				sHref = elmTarget.href;&lt;br /&gt;
 				sTitle = elmTarget.textContent;&lt;br /&gt;
 			}&lt;br /&gt;
 			var iSearchIndex = arSavedSearches.find(getCurrentSearchText());&lt;br /&gt;
 			if (iSearchIndex == -1) {&lt;br /&gt;
 				addCurrentSearch();&lt;br /&gt;
 				iSearchIndex = arSavedSearches.length - 1;&lt;br /&gt;
 			}&lt;br /&gt;
 			GM_setValue('searchresult.' + iSearchIndex,&lt;br /&gt;
 				sHref + ' ' + sTitle);&lt;br /&gt;
 		}&lt;br /&gt;
 &lt;br /&gt;
 		'''function watchLocation(sPropertyName, sOldValue, sNewValue) {'''&lt;br /&gt;
 '''			trackClick(sNewValue);'''&lt;br /&gt;
 '''			return sNewValue;'''&lt;br /&gt;
 		}&lt;br /&gt;
 &lt;br /&gt;
 		if (/^\/search/.test(window.location.pathname)) {&lt;br /&gt;
 			injectRecentSearches();&lt;br /&gt;
 			addCurrentSearch();&lt;br /&gt;
 			document.addEventListener('click', trackClick, true);&lt;br /&gt;
 			'''var unsafeDocument = document.wrappedJSObject || document;'''&lt;br /&gt;
 '''			unsafeDocument.watch('location', watchLocation);'''&lt;br /&gt;
 '''			unsafeDocument.location.watch('href', watchLocation);'''&lt;br /&gt;
 '''			unsafeWindow.watch('location', watchLocation);'''&lt;br /&gt;
 '''			unsafeWindow.location.watch('href', watchLocation);'''	&lt;br /&gt;
 		} else if (/^\/$/.test(window.location.pathname)) {&lt;br /&gt;
 			injectRecentSearches();&lt;br /&gt;
 		}&lt;br /&gt;
&lt;br /&gt;
=== Running the Hack ===&lt;br /&gt;
&lt;br /&gt;
As I mentioned before, the only reason for this hack is to get two previous hacks to play nicely with each other. After you install this script (Tools → Install This User Script), you also need to install ''google-searchkeys.user.js'' from &amp;quot;Add Keyboard Shortcuts to Google Search Results&amp;quot; [[Greasemonkey Hacks/Search#Add Keyboard Shortcuts to Google Search Results|'''[Hack #58]''']].&lt;br /&gt;
&lt;br /&gt;
Now, go to ''http://www.google.com'' and search for anything. In the search results page, you will see the numbers to the left of each search result, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-22|Figure 6-22]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;tip&amp;quot;&amp;gt;&lt;br /&gt;
'''Tip'''&lt;br /&gt;
&lt;br /&gt;
You won't see any recent searches unless you've previously installed the Recent Searches hack and executed a search.&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-22&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-22. Google search with keyboard shortcuts'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt266.png|Google search with keyboard shortcuts]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Click through to one of the search result pages by typing the number next to the link.&lt;br /&gt;
&lt;br /&gt;
Go back to ''http://www.google.com''. Below the search box is the list of recent searches, as shown in [[Greasemonkey Hacks/Search#greasemonkeyhks-CHP-6-FIG-23|Figure 6-23]].&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div id=&amp;quot;greasemonkeyhks-CHP-6-FIG-23&amp;quot;&amp;gt;&lt;br /&gt;
'''Figure 6-23. Recent searches executed with the keyboard'''&lt;br /&gt;
&lt;br /&gt;
[[Image:Greasemonkey Hacks_I_6_tt267.png|Recent searches executed with the keyboard]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The list of recent searches includes the result link you just followed by typing the Google SearchKeys keyboard shortcut.&lt;/div&gt;</description>
			<pubDate>Tue, 11 Mar 2008 20:29:52 GMT</pubDate>			<dc:creator>Docbook2Wiki</dc:creator>			<comments>http://commons.oreilly.com/wiki/index.php/Talk:Greasemonkey_Hacks/Search</comments>		</item>
	</channel>
</rss>