How to Remember Recent Google Searches with User Scripts

Remember Recent Google Searches

Track what you search for and which search results you follow.

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!

This hack lets me do what I had hoped the "My Search History" tool would do.

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.

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  ' +
   '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( );
 }   

Running the Hack

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 Figure 6-19. Clicking on a recent search term will take you to the same link you followed when you originally executed the search.

Figure 6-19. Recent searches on the Google home page

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 Figure 6-20.

Figure 6-20. Recent searches on Google's search results page

Hacking the Hack

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 injectRecentSearches function, find these two lines:

 var sHref = sSearchResult.substring(0, iSpacePos);
 var sTitle = sSearchResult.substring(iSpacePos + 1);

And change them like this:

 var sHref = 'http://www.google.com/search?q=' + escape(sSearchResult); 
 var sTitle = 'previously found ' + sSearchResult.substring(iSpacePos + 1) + 
  '\n' + sSearchResult.substring(0, iSpacePos);

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.