O'Reilly Hacks
oreilly.comO'Reilly NetworkSafari BookshelfConferences Sign In/My Account | View Cart   
Book List Learning Lab PDFs O'Reilly Gear Newsletters Press Room Jobs  


 
Buy the book!
Greasemonkey Hacks
By Mark Pilgrim
November 2005
More Info

HACK
#57
Remember Recent Google Searches
Track what you search for and which search results you follow
The Code
[Discuss (0) | Link to this hack]

The Code

This user script runs on all Google pages. The code itself breaks down into three distinct parts:

  1. The SavedSearches function and associated prototype methods are used to create a persistent array—i.e., an Array class that saves its data to the Firefox preferences database.

  2. The getCurrentSearchText, addCurrentSearch, clearSavedSearches, and injectRecentSearches 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.

  3. The trackClick function is where the real magic happens. On search result pages, we register trackClick as a global onclick event handler. When you click on anything on the search results page, trackClick 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.

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.

WARNING

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 GM_xmlhttpRequest function, and you have a recipe for catastrophic privacy violations.

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.

Save the following user script as recentsearches.user.js:

	// ==UserScript==
	// @name         Recent Searches
	// @namespace	 http://diveintomark.org/projects/greasemonkey/
	// @description  remember and display recent Google searches
	// @include		 http://www.google.com/*
	// ==/UserScript==

	function SavedSearches() {
		var iCount = GM_getValue('count') || 0;
		for (var i = 0; i < iCount; i++) {
			this.push({
				"searchtext": GM_getValue('searchtext.' + i, ''),
				"searchresult": GM_getValue('searchresult.' + i, '')});
		}
	}
	SavedSearches.prototype = new Array( );

	SavedSearches.prototype.find = function(sSearchText) {
		for (var i = this.length - 1; i >= 0; i--) {
			if (this[i] == sSearchText) {
				return i;
			}
		}
		return -1;
	};

	SavedSearches.prototype.append = function(sSearchText) {
		GM_setValue('searchtext.' + this.length, sSearchText);
		this.push({"searchtext": sSearchText});
		GM_setValue('count', this.length);
	};

	var arSavedSearches = new SavedSearches( );

	function getCurrentSearchText( ) {
		var elmForm = document.forms.namedItem('gs');
		if (!elmForm) { return; }
		var elmSearchBox = elmForm.elements.namedItem('q');
		if (!elmSearchBox) { return; }
		var sKeyword = elmSearchBox.value;	
		if (!sKeyword) { return; }
		return sKeyword;
	}

	function addCurrentSearch( ) {
		var sCurrentSearchText = getCurrentSearchText( );
		if (!sCurrentSearchText) { return; }
		var sLastSearch = null;
		if (arSavedSearches.length) {
			sLastSearch = arSavedSearches[arSavedSearches.length - 1];
		}
		if (sLastSearch &&
			(sLastSearch['searchtext'] == sCurrentSearchText)) {
			return;
		}
		arSavedSearches.append(sCurrentSearchText);
	}

	function clearSavedSearches( ) {
		for (var i = 0; i < arSavedSearches.length; i++) {
			GM_setValue('searchtext.' + i, '');
			GM_setValue('searchresult.' + i, '');
		}
		GM_setValue('count', 0);
		arSavedSearches = new SavedSearches( );
		var elmRecentSearches = document.getElementById('recentsearcheslist');
		if (elmRecentSearches) {
			elmRecentSearches.innerHTML = '';
		}			
	}

	function injectRecentSearches( ) {
		if (!arSavedSearches.length) { return; }
		var elmFirst = document.evaluate("//table[@bgcolor='#e5ecf9']",
			document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,
			null).singleNodeValue;
		if (!elmFirst) {
			elmFirst = document.evaluate("//form[@name='f']",
				document, null, XPathResult.FIRST_ORDERED_NODE_TYPE,
				null).singleNodeValue;
		}
		if (!elmFirst) { return; }
		var htmlRecentSearches = '<p style="font-size: small">Recent searches:
';
		var iDisplayedCount = 0;
		for (var i = arSavedSearches.length - 1;
			(iDisplayedCount <10) && (i >= 0); i--) {
			var oSearch = arSavedSearches[i];
			if (!oSearch['searchresult']) { continue; }
			var sSearchResult = oSearch['searchresult'];
			var iSpacePos = sSearchResult.indexOf(' ');
			var sHref = sSearchResult.substring(0, iSpacePos);
			var sTitle = sSearchResult.substring(iSpacePos + 1);
			htmlRecentSearches += '<a href="' + sHref + '" title="' +
				sTitle + '">' + oSearch['searchtext'] + '</a> &middot; ';
			iDisplayedCount++;
		}
		if (!iDisplayedCount) { return; }
		htmlRecentSearches += '[<a id="clearsavedsearches" ' +
			'title="Clear saved searches" href="#">clear</a>]</p>';
		var elmWrapper = document.createElement('div');
		elmWrapper.id = "recentsearcheslist";
		elmWrapper.innerHTML = htmlRecentSearches;
		elmFirst.parentNode.insertBefore(elmWrapper, elmFirst.nextSibling);
		window.addEventListener('load', function( ) {
			var elmClearLink = document.getElementById('clearsavedsearches');
			elmClearLink.addEventListener('click', clearSavedSearches, true);
		}, true);
	}

	function trackClick(event) {
		var sHref, sTitle;
		var elmTarget = event.target;
		while ((elmTarget.nodeName != 'A') &&
				(elmTarget.nodeName != 'BODY')) {
			elmTarget = elmTarget.parentNode;
		}
		if (elmTarget.nodeName != 'A') { return; }
		var elmParent = elmTarget.parentNode;
		while ((elmParent.nodeName != 'P') &&
			(elmParent.nodeName != 'BODY')) {
			elmParent = elmParent.parentNode;
		}
		if (elmParent.nodeName != 'P') { return; }
		if (elmParent.getAttribute('class') != 'g') { return; }	
		sHref = elmTarget.href;
		sTitle = elmTarget.textContent;
		var iSearchIndex = arSavedSearches.find(getCurrentSearchText( ));
		if (iSearchIndex == -1) {
			addCurrentSearch( );
			iSearchIndex = arSavedSearches.length - 1;
		}
		GM_setValue('searchresult.' + iSearchIndex,
				sHref + ' ' + sTitle);
	}

	if (/^\/search/.test(location.pathname)) {
		injectRecentSearches( );
		addCurrentSearch( );	
		document.addEventListener('click', trackClick, true);
	} else if (/^\/$/.test(location.pathname)) {
		injectRecentSearches( );
	}			


O'Reilly Home | Privacy Policy

© 2007 O'Reilly Media, Inc.
Website: | Customer Service: | Book issues:

All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.