There are 4 previous versions of this script.
Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)
// Google Cache Continue Redux
// Based on Google Cache Continue by Jonathon Ramsey
// v0.5
// Copyright (C) 2005-2011 by
// Jonathon Ramsey (jonathon.ramsey@gmail.com)
// Jeffery To (jeffery.to@gmail.com)
// This file is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published
// by the Free Software Foundation; either version 2, or (at your
// option) any later version.
// This file is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this software; see the file COPYING. If not, write to
// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.
// (Comment by Jonathon Ramsey:)
// Thanks to Neil Klopfenstein for a fix to deal with the text only
// version of cached pages
// ==UserScript==
// @name Google Cache Continue Redux
// @version 0.5
// @namespace http://www.thingsthemselves.com/greasemonkey/
// @description Add links to Google cache results to allow continuing using the cache, keeping search terms highlighted
// @include http://*/search?*q=cache:*
// @include http://*/search?*q=cache%3A*
// @include http://*/search?*q=cache%3a*
// @include https://*/search?*q=cache:*
// @include https://*/search?*q=cache%3A*
// @include https://*/search?*q=cache%3a*
// ==/UserScript==
// Redux changelog:
// v0.5 (2011-05-25)
// - Works with cache pages under HTTPS / SSL
// - Added an option to always use HTTPS
// - Options can be saved in Chrome, if the cache page comes from webcache.googleusercontent.com
// - Added a function to sync options between HTTP and HTTPS cache pages in Chrome
// Must be manually triggered since it requires an iframe page load
// - Cache link text change takes effect immediately, instead of after page reload
// - Added default styles to our elements so that they're more resistant to in-page styles
// - Added an About panel
// - Added a "Check for updates" function, inside the About panel (fails silently in Chrome due to browser restrictions)
// - Auto-check for updates every 60 days
// - Another refactoring
// v0.4 (2010-02-10)
// - Google Chrome compatibility (thanks Norman Rasmussen)
// - Added option to redirect page links to the Google cache, instead of adding cache links
// - Added options for cache link text and background colour
// - Added options panel (cache link options not shown in Google Chrome since options cannot be saved)
// - Should work for all language versions of Google (noCacheTitle, textOnlyLinkText and fullLinkText options no longer necessary)
// - Refactored code
// v0.3 (2008-11-27)
// - Fixed duplicate "http://" in uncached link when search URL includes the protocol
// v0.2 (2008-08-19)
// - Externalized strings in about:config prefs
// v0.1 (2008-07-31)
// - Initial version
(function( window, document, head, body, undefined ) {
/*
* user editable parts, start!
*/
// default options
var defaultOptions = {
// whether page links should be redirected
// can be changed in the options panel
redirectPageLinks: false,
// whether we should always use HTTPS for cache pages
useHttps: false,
// the rest of these can be changed in the options panel if the browser supports GM_setValue()
// link text for cache links
cacheLinkText: 'cache',
// background colour for cache links
cacheLinkBackgroundColor: 'yellow',
// text colour for cache links
cacheLinkTextColor: 'red'
},
// other strings
// these can only be changed by editing this script
strings = {
// explanation text for uncached link, for when Google does not have a cached version of the page
// %s will be replaced by a link to the original (uncached) page
uncached: '<b>Uncached:</b> %s',
// explanation text for cache links
// %s will be replaced by a sample cache link
cacheLinkExplanation: 'Use %s links to continue using the Google cache.',
// explanation text for redirected page links
redirectLinkExplanation: 'All HTTP/HTTPS links will be redirected to the Google cache.',
// "Show options" link text
showOptions: 'Show options',
// "Hide options" link text
hideOptions: 'Hide options',
// redirect page links option label
redirectPageLinksLabel: 'Redirect links to the Google cache',
// use https option label
useHttpsLabel: 'Always use HTTPS',
// cache link options heading
cacheLinkOptions: 'Cache link',
// cache link text option label
cacheLinkTextLabel: 'Link text:',
// cache link background colour option label
cacheLinkBackgroundColorLabel: 'Background colour:',
// cache link text colour option label
cacheLinkTextColorLabel: 'Text colour:',
// instruction text for text options
textOptionInstructions: 'Leave a field blank to reset to default',
// synchronize http / https options link text
// %s will be replaced by the appropriate protocol (HTTP/HTTPS)
syncLinkText: 'Copy options to %s Google cache',
// synchronizing text
syncing: 'Copying…',
// sync done text
syncDone: 'done!',
// if the cache page host matches this, then we can save options in Chrome
// http and https pages will have separate options though :-(
cacheHost: 'webcache.googleusercontent.com',
// prefix for values stored in localStorage
localStoragePrefix: 'greasemonkey.scriptvals.http://www.thingsthemselves.com/greasemonkey//Google Cache Continue Redux.',
// "About" link text
about: 'About',
// about title text
aboutTitle: 'Google Cache Continue Redux',
// about text
aboutText: 'Based on Google Cache Continue by Jonathon Ramsey<br>Copyright 2005-2011 by Jonathon Ramsey and Jeffery To',
// about version text
// %s will be replaced by the version number
version: 'Version %s',
// about homepage link text
homepage: 'Homepage',
// about homepage url
homepageUrl: 'http://userscripts.org/scripts/show/30878',
// "Close" link text
close: 'Close',
// check for update link text
check: 'Check for updates',
// checking text
checking: 'Checking for updates…',
// install newer version link text
update: 'Install latest version',
// meta url
metaUrl: 'http://userscripts.org/scripts/source/30878.meta.js'
},
// modify these to change the appearance of things
css = {
cacheLink: {
'font-size': 'x-small',
'font-weight': 'bold',
'margin': '0.3ex',
'padding': '0 0.6ex 0.4ex 0.3ex',
'text-decoration': 'none'
},
uncached: {
'border': '1px solid #3366cc',
'padding': '1ex 0.5ex'
},
explanation: {
'border': '1px solid #999',
'margin': '1em 0',
'padding': '1ex 0.5ex'
},
panel: {
'margin-top': '0.5em'
},
defaults: {
// used to reset styles
reset: {
'background': 'none',
'border': '0',
'clear': 'none',
'clip': 'auto',
'color': '#000',
'content': 'normal',
'cursor': 'auto',
'float': 'none',
'font': '13px arial,sans-serif',
'font-stretch': 'normal',
'font-size-adjust': 'none',
'height': 'auto',
'letter-spacing': 'normal',
'list-style': 'none',
'margin': '0',
'overflow': 'visible',
'padding': '0',
'position': 'static',
'text-align': 'left',
'text-decoration': 'none',
'text-indent': '0',
'text-transform': 'none',
'vertical-align': 'baseline',
'visibility': 'visible',
'white-space': 'normal',
'width': 'auto',
'word-spacing': 'normal',
'z-index': 'auto'
},
// styles for each element type
a: {
'cursor': 'pointer',
'color': '#00c',
'display': 'inline',
'text-decoration': 'underline'
},
b: {
'display': 'inline',
'font-weight': 'bold'
},
div: {
'display': 'block',
},
input_checkbox: {
'display': 'inline',
'margin': '3px',
'vertical-align': 'middle'
},
input_text: {
'background': '#fff',
'border': '1px solid #999',
'cursor': 'text',
'display': 'inline',
'margin': '1px 0',
'padding': '2px 1px',
'text-align': 'start'
},
label: {
'cursor': 'default',
'display': 'inline',
'vertical-align': 'middle'
},
table: {
'display': 'table',
'margin': '0.5em 0',
'border-collapse': 'collapse'
},
td: {
'display': 'table-cell',
'padding-right': '5px'
},
th: {
'display': 'table-cell',
'font-weight': 'bold'
},
p: {
'display': 'block',
'margin': '1em 0'
}
}
};
/*
* user editable parts, end!
*/
/*
* poor-man's jQuery!
*/
var $ = function( string, context ) {
var div, el;
if ( string.indexOf( '<' ) > -1 ) {
div = document.createElement( 'div' );
div.innerHTML = $.trim( string );
el = div.firstChild;
div.removeChild( el );
return el;
}
if ( string.indexOf( '#' ) === 0 ) {
el = document.getElementById( string.substring(1) );
if ( el && context && !( context.compareDocumentPosition( el ) & 16 ) ) {
el = undefined;
}
return el ? [ el ] : [];
}
return ( context || document ).getElementsByTagName( string );
};
$.trim = function( string ) { return ( '' + string ).replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' ); };
$.each = function( object, fn ) {
var length, i;
if ( object instanceof Array ) {
length = object.length;
for ( i = 0; i < length; i++ ) {
if ( fn.call( object[ i ], i, object[ i ] ) === false ) {
break;
}
}
} else if ( typeof object === 'object' && object ) {
for ( i in object ) {
if ( fn.call( object[ i ], i, object[ i ] ) === false ) {
break;
}
}
}
};
$.makeArray = function( object ) { return Array.prototype.slice.call( object, 0 ); };
$.extend = function( target ) {
var args = $.makeArray( arguments );
target = target || {};
args.shift();
$.each( args, function( i, source ) {
var prop;
source = source || {};
for ( prop in source ) {
target[ prop ] = source[ prop ];
}
} );
return target;
};
$.insertAfter = function( newChild, refChild ) { refChild.parentNode.insertBefore( newChild, refChild.nextSibling ); };
/*
* main!
*/
// (encoded) search query (contains cache term)
var searchQuery = findSearchQuery(),
// (encoded) cache term ("cache%3Ahttp%3A%2F%2Fwww.example.com")
cacheTerm = findCacheTerm( searchQuery ),
// element ids
id = generateIds( 'cacheLink hideCacheLinks cacheLinkColors cacheLinkHoverColors exampleCacheLink message optionsLink options redirectPageLinks useHttps cacheLinkText cacheLinkBackgroundColor cacheLinkTextColor syncLink syncing syncDone syncIframe aboutLink about closeLink checkLink checking updateLink'.split( ' ' ) ),
// script version
version = '0.5',
// true if we're in currently checking
checking = false,
// true of we're using localStorage to save options
usingLocalStorage = false,
// true if we are syncing options
syncing = false,
// true if we're under http (false for https)
isHttp = window.location.protocol === 'http:',
// script options
options,
// link details
links;
// we can't continue without this information
if ( !searchQuery || !cacheTerm ) {
return;
}
// these aren't actual options but things we need to remember
$.extend( defaultOptions, {
lastUpdateCheck: '0', // GM_setValue can only store 32-bit integers, so we need to save this as a string
updateAvailable: false
} );
// restore, then save, options
// restoreOptions() will set usingLocalStorage as a side effect
// should find a better way...
options = restoreOptions();
saveOptions( options );
if ( isCachePage( searchQuery ) ) {
if ( options.useHttps && isHttp ) {
window.setTimeout( function() { window.location.replace( window.location.href.replace( /^http:/i, 'https:' ) ); }, 0 );
} else {
links = scanLinks( cacheTerm );
if ( links.changeVersion ) {
addExplanation( options, links.changeVersion.parentNode.parentNode );
if ( options.canCheckForUpdate && shouldCheckForUpdate( options ) ) {
checkForUpdate( options );
}
}
}
} else {
// if Google hasn't cached the original page, add a link for the original URL
addOriginalLink( decodeURIComponent( cacheTerm ).replace( /^cache:/, '' ) );
}
if ( usingLocalStorage ) {
window.addEventListener( 'message', receivedMessage, false );
}
// cleanup
window.addEventListener( 'unload', function() {
window.removeEventListener( 'unload', arguments.callee, false );
window.removeEventListener( 'message', receivedMessage, false );
searchQuery = cacheTerm = options = id = links = null;
}, false );
/*
* functions!
*/
/*
* save / restore options
*/
// returns true if the browser supports GM_getValue / GM_setValue
function canSaveOptions() {
var me = arguments.callee, test, result;
if ( me.cached === undefined ) {
test = function() {
var name = 'testOption', token = getToken(), value;
try {
GM_setValue( name, token );
value = GM_getValue( name );
GM_setValue( name, '' );
} catch ( e ) {
value = null;
}
return value === token;
};
result = test();
// use localStorage to save options if host matches cacheHost
// based on by http://userscripts.org/topics/41177#posts-197125
if ( !result && window.location.host === strings.cacheHost && window.localStorage &&
typeof window.localStorage.getItem === 'function' && typeof window.localStorage.setItem === 'function' ) {
GM_getValue = function( name, defaultValue ) {
var value = window.localStorage.getItem( strings.localStoragePrefix + name ), type;
if ( value ) {
type = value.charAt( 0 );
value = value.substring( 1 );
switch ( type ) {
case 'b':
value = value === 'true';
break;
case 'n':
value = Number( value );
break;
}
} else {
value = defaultValue;
}
return value;
};
GM_setValue = function( name, value ) {
window.localStorage.setItem( strings.localStoragePrefix + name, ( typeof value ).charAt( 0 ) + value );
};
result = test();
usingLocalStorage = result;
}
me.cached = result;
}
return me.cached;
}
// returns an options object based on any saved options and the default options
function restoreOptions() {
var options = $.extend( {}, defaultOptions ), canSave = canSaveOptions();
if ( canSave ) {
$.each( defaultOptions, function( name ) {
var saved = GM_getValue( name );
if ( saved !== undefined ) {
options[ name ] = saved;
}
} );
}
// don't check for updates if we can't remember when we last checked
options.canCheckForUpdate = typeof GM_xmlhttpRequest === 'function' && canSave;
return options;
}
// saves the given options object
function saveOptions( options ) {
if ( canSaveOptions() ) {
$.each( defaultOptions, function( name ) { GM_setValue( name, options[ name ] ); } );
}
}
/*
* find information from the current page
*/
// returns the search query from the page URL
function findSearchQuery() {
var query = '';
$.each( window.location.search.replace( /^\?/, '' ).split( '&' ), function( i, pair ) {
if ( pair.indexOf( 'q=' ) === 0 ) {
query = pair.substring( 2 );
return false;
}
} );
return query;
}
// returns the (encoded) cache term ("cache:...") from the given query parameter
function findCacheTerm( query ) {
var cacheTerm = '';
$.each( ( query || '' ).split( '+' ), function( i, encoded ) {
if ( decodeURIComponent( encoded ).indexOf( 'cache:' ) === 0 ) {
cacheTerm = encoded;
return false;
}
} );
return cacheTerm;
}
// returns true if the current page is a google cache page
function isCachePage( query ) {
var str = document.title,
prefix = decodeURIComponent( query.replace( /\+/g, ' ' ) ) + ' - ',
result = true;
if ( str.indexOf( prefix ) === 0 ) {
str = str.replace( prefix, '' );
result = str.indexOf( 'Google' ) === -1 || str.indexOf( ' - ' ) > -1;
}
return result;
}
// finds text-only / full version link, gathers link details, inserts cache links
function scanLinks( cacheTerm ) {
// get a snapshot from the live DOM
var links = document.evaluate( '//a[@href]',
document,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null ),
list = [],
tmplHref = window.location.href.replace(window.location.hash, ''),
changeVersion, link, href, hash, cacheHref, cacheLink, i;
for ( i = 0; ( link = links.snapshotItem( i ) ); i++ ) {
if ( !changeVersion ) {
// find last link in the cache page header (the "Text-only version" or "Full version" link)
if ( link.pathname === '/search' && /cache:.*&strip=[01]$/.test( link.search ) ) {
changeVersion = link;
}
} else {
// gather link details, insert cache link
if ( /^https?:$/.test( link.protocol ) ) {
href = link.href;
hash = link.hash;
cacheHref = tmplHref.replace( cacheTerm, encodeURIComponent( 'cache:' + href.replace( hash, '' ) ) ) + hash;
cacheLink = $( '<a href="' + cacheHref + '" class="' + id.cacheLink + '" ' + getInlineStyle( 'a', css.cacheLink ) + '>' + options.cacheLinkText + '</a>' );
list.push( {
link: link,
cacheLink: cacheLink,
href: href,
cacheHref: cacheHref,
secureCacheHref: cacheHref.replace( /^http:/i, 'https:' )
} );
$.insertAfter( cacheLink, link );
}
}
}
list.changeVersion = changeVersion;
return list;
}
/*
* manipulate styles
*/
// returns a style element for the given selector and rules
function getStyleElement( selector, rules, id ) {
var buf = [ '<style id="', id || '', '" type="text/css">', selector, '{' ];
$.each( rules, function( prop, val ) { buf.push( prop, ':', val, ';' ); } );
buf.push('}</style>');
return $( buf.join( '' ) );
}
// returns a string for the given tag and rules object
function getInlineStyle( tag, rules ) {
var buf = [ 'style="' ];
if ( tag !== false ) {
rules = $.extend( {}, css.defaults.reset, css.defaults[ tag ], rules || {} );
}
$.each( rules, function( prop, val ) { buf.push( prop, ':', val, ';' ); } );
buf.push('"');
return buf.join( '' );
}
// make cache links visible or hidden
function setCacheLinkVisibility( isVisible ) {
var style = $( '#' + id.hideCacheLinks )[ 0 ];
if ( !style ) {
style = getStyleElement( 'a.' + id.cacheLink, { 'display': 'none !important' }, id.hideCacheLinks );
head.appendChild( style );
}
style.disabled = style.sheet.disabled = isVisible;
}
// set colours for cache links
function setCacheLinkColors() {
var prevNormal = $( '#' + id.cacheLinkColors )[ 0 ],
prevHover = $( '#' + id.cacheLinkHoverColors )[ 0 ],
curNormal = getStyleElement( 'a.' + id.cacheLink, {
'background': options.cacheLinkBackgroundColor + ' !important',
'color': options.cacheLinkTextColor + ' !important'
}, id.cacheLinkColors ),
curHover = getStyleElement( 'a.' + id.cacheLink + ':hover', {
'background': options.cacheLinkTextColor + ' !important',
'color': options.cacheLinkBackgroundColor + ' !important'
}, id.cacheLinkHoverColors );
if ( prevNormal ) {
head.replaceChild( curNormal, prevNormal );
} else {
head.appendChild( curNormal );
}
if ( prevHover ) {
head.replaceChild( curHover, prevHover );
} else {
head.appendChild( curHover );
}
}
/*
* manipulate html
*/
// updates hrefs for links
function updateLinkHrefs( list, options ) {
var cacheLinkProp = options.useHttps ? 'secureCacheHref' : 'cacheHref',
linkProp = options.redirectPageLinks ? cacheLinkProp : 'href';
$.each( list, function() {
this.link.href = this[ linkProp ];
this.cacheLink.href = this[ cacheLinkProp ];
} );
}
// updated link text for cache links
function updateCacheLinkText( list, text ) {
$( '#' + id.exampleCacheLink )[ 0 ].innerHTML = text;
$.each( list, function() { this.cacheLink.innerHTML = text; } );
}
// adds a link for the original URL to the Google search results page
// safer to add after the list of suggestions
// should be safe to not add a lot of default styles here
function addOriginalLink( url ) {
var ul = $( 'ul' )[ 0 ],
msg = strings.uncached.replace( /%s/g, '<a href="' + ( url.indexOf( '://' ) === -1 ? 'http://' : '' ) + url + '">' + url + '</a>' );
if ( ul ) {
$.insertAfter( $( '<p ' + getInlineStyle( false, css.uncached ) + '>' + msg + '</p>' ), ul );
}
}
// adds our explanation text and option panel to the cache page header
function addExplanation( options, container ) {
var space = ' ', link, input;
// OMG this is ugly
container.appendChild( $( [
'<div ', getInlineStyle( 'div', css.explanation ), '>',
'<span id="', id.message, '"></span>',
space,
'<a href="" id="', id.optionsLink, '" ', getInlineStyle( 'a' ), '></a>',
space,
'<a href="" id="', id.aboutLink, '" ', getInlineStyle( 'a' ), '>',
strings.about,
'</a>',
space,
'<span id="', id.checking, '">',
strings.checking,
'</span>',
'<a href="', strings.homepageUrl, '" id="', id.updateLink, '" ', getInlineStyle( 'a' ), ' target="_blank">',
strings.update,
'</a>',
'<div id="', id.about, '" ', getInlineStyle( 'div', css.panel ), '>',
'<p ', getInlineStyle( 'p' ), '>',
'<b ', getInlineStyle( 'b' ), '>',
strings.aboutTitle,
'</b>',
'<br>',
strings.version.replace(/%s/g, version ),
'<br>',
'<a href="', strings.homepageUrl, '" target="_blank" ', getInlineStyle( 'a' ), '>',
strings.homepage,
'</a>',
space,
'<a href="" id="', id.checkLink, '" ', getInlineStyle( 'a' ), '>',
strings.check,
'</a>',
'</p>',
'<p ', getInlineStyle( 'p' ), '>',
strings.aboutText,
'</p>',
'<a href="" id="', id.closeLink, '" ', getInlineStyle( 'a' ), '>', strings.close, '</a>',
'</div>',
'<div id="', id.options, '" ', getInlineStyle( 'div', css.panel ), '>',
'<input type="checkbox" id="', id.redirectPageLinks, '" ', getInlineStyle( 'input_checkbox' ), ' />',
'<label for="', id.redirectPageLinks, '" ', getInlineStyle( 'label' ), '>',
strings.redirectPageLinksLabel,
'</label>',
'<br>',
'<input type="checkbox" id="', id.useHttps, '" ', getInlineStyle( 'input_checkbox' ), ' />',
'<label for="', id.useHttps, '" ', getInlineStyle( 'label' ), '>',
strings.useHttpsLabel,
'</label>',
canSaveOptions() ? [
'<table cellpadding="0" cellspacing="0" border="0" ', getInlineStyle( 'table' ), '>',
'<tr>',
'<th colspan="2" ', getInlineStyle( 'th' ), '>',
strings.cacheLinkOptions,
'</th>',
'</tr>',
'<tr>',
'<td ', getInlineStyle( 'td' ) + '>',
'<label for="', id.cacheLinkText, '" ', getInlineStyle( 'label' ), '>',
strings.cacheLinkTextLabel,
'</label>',
'</td>',
'<td ', getInlineStyle( 'td' ) + '>',
'<input type="text" id="', id.cacheLinkText, '" value="', options.cacheLinkText.replace( /"/g, '"' ), '" ', getInlineStyle( 'input_text' ) + ' />',
'</td>',
'</tr>',
'<tr>',
'<td ', getInlineStyle( 'td' ) + '>',
'<label for="', id.cacheLinkBackgroundColor, '" ', getInlineStyle( 'label' ) + '>',
strings.cacheLinkBackgroundColorLabel,
'</label>',
'</td>',
'<td ', getInlineStyle( 'td' ) + '>',
'<input type="text" id="', id.cacheLinkBackgroundColor, '" value="', options.cacheLinkBackgroundColor.replace( /"/g, '"' ), '" ', getInlineStyle( 'input_text' ) + ' />',
'</td>',
'</tr>',
'<tr>',
'<td ', getInlineStyle( 'td' ) + '>',
'<label for="', id.cacheLinkTextColor, '" ', getInlineStyle( 'label' ) + '>',
strings.cacheLinkTextColorLabel,
'</label>',
'</td>',
'<td ', getInlineStyle( 'td' ) + '>',
'<input type="text" id="', id.cacheLinkTextColor, '" value="', options.cacheLinkTextColor.replace( /"/g, '"' ), '" ', getInlineStyle( 'input_text' ) + ' />',
'</td>',
'</tr>',
'</table>',
strings.textOptionInstructions,
usingLocalStorage ? [
'<br>',
'<a href="" id="', id.syncLink, '" ', getInlineStyle( 'a' ), '>',
strings.syncLinkText.replace( /%s/g, isHttp ? 'HTTPS' : 'HTTP' ),
'</a>',
'<span id="', id.syncing, '" style="display:none;">',
strings.syncing,
'</span>',
'<span id="', id.syncDone, '" style="display:none;"> ',
strings.syncDone,
'</span>'
].join( '' ) : '',
].join( '' ) : '',
'</div>',
'</div>'
].join( '' ) ) );
// options link
link = $( '#' + id.optionsLink )[ 0 ];
link.addEventListener( 'click', optionsLinkClick, false );
optionsLinkClick.call( link );
// redirect page links checkbox
input = $( '#' + id.redirectPageLinks )[ 0 ];
input.checked = options.redirectPageLinks;
input.addEventListener( 'click', redirectPageLinksClick, false );
redirectPageLinksClick.call( input );
// use https checkbox
input = $( '#' + id.useHttps )[ 0 ];
input.checked = options.useHttps;
input.addEventListener( 'click', useHttpsClick, false );
useHttpsClick.call( input );
setCacheLinkColors();
// other options
if ( canSaveOptions() ) {
$( '#' + id.cacheLinkText )[ 0 ].addEventListener( 'change', cacheLinkTextChange, false );
$( '#' + id.cacheLinkBackgroundColor )[ 0 ].addEventListener( 'change', cacheLinkBackgroundColorChange, false );
$( '#' + id.cacheLinkTextColor )[ 0 ].addEventListener( 'change', cacheLinkTextColorChange, false );
if ( usingLocalStorage ) {
$( '#' + id.syncLink )[ 0 ].addEventListener( 'click', syncLinkClick, false );
}
}
// about and close links
link = $( '#' + id.aboutLink )[ 0 ];
link.addEventListener( 'click', aboutLinkClick, false );
link = $( '#' + id.closeLink )[ 0 ];
link.addEventListener( 'click', aboutLinkClick, false );
aboutLinkClick.call( link );
// check link
$( '#' + id.checkLink )[ 0 ].addEventListener( 'click', checkLinkClick, false );
reflectUpdateStatus( options );
window.addEventListener( 'unload', function() {
window.removeEventListener( 'unload', arguments.callee, false );
$( '#' + id.optionsLink )[ 0 ].removeEventListener( 'click', optionsLinkClick, false );
$( '#' + id.redirectPageLinks )[ 0 ].removeEventListener( 'click', redirectPageLinksClick, false );
$( '#' + id.useHttps )[ 0 ].removeEventListener( 'click', useHttpsClick, false );
if ( canSaveOptions() ) {
$( '#' + id.cacheLinkText )[ 0 ].removeEventListener( 'change', cacheLinkTextChange, false );
$( '#' + id.cacheLinkBackgroundColor )[ 0 ].removeEventListener( 'change', cacheLinkBackgroundColorChange, false );
$( '#' + id.cacheLinkTextColor )[ 0 ].removeEventListener( 'change', cacheLinkTextColorChange, false );
if ( usingLocalStorage ) {
$( '#' + id.syncLink )[ 0 ].removeEventListener( 'click', syncLinkClick, false );
}
}
$( '#' + id.aboutLink )[ 0 ].removeEventListener( 'click', aboutLinkClick, false );
$( '#' + id.closeLink )[ 0 ].removeEventListener( 'click', aboutLinkClick, false );
$( '#' + id.checkLink )[ 0 ].removeEventListener( 'click', checkLinkClick, false );
}, false);
}
/*
* check for update
*/
// check every 60 days
function shouldCheckForUpdate( options ) { return now() - parseInt( options.lastUpdateCheck, 10 ) >= 60 * 24 * 60 * 60 * 1000; }
function checkForUpdate( options ) {
options.lastUpdateCheck = now() + '';
saveOptions( options );
checking = true;
showChecking();
function after() {
checking = false;
reflectUpdateStatus( options );
}
try {
GM_xmlhttpRequest( {
method: 'GET',
url: strings.metaUrl + '?_=' + now(),
onload: function( data ) {
var a = /\/\/[ \t]*@version[ \t]+([^\s]+)/.exec( data.responseText || '' );
if ( data.status === 200 ) {
options.updateAvailable = !!( a && a[ 1 ] > version );
saveOptions( options );
}
after();
},
onerror: after
} );
} catch ( e ) {
after();
}
}
function showChecking() {
$( '#' + id.checking )[ 0 ].style.display = 'inline';
$( '#' + id.updateLink )[ 0 ].style.display = 'none';
$( '#' + id.checkLink )[ 0 ].style.display = 'none';
}
function reflectUpdateStatus( options ) {
$( '#' + id.checking )[ 0 ].style.display = 'none';
$( '#' + id.updateLink )[ 0 ].style.display = options.updateAvailable ? 'inline' : 'none';
$( '#' + id.checkLink )[ 0 ].style.display = options.canCheckForUpdate ? 'inline' : 'none';
}
/*
* synchronize options (between http and https)
*/
// we need to use execScript() here because Chrome won't let an extension on the parent window access the window object of an iframe
// (or an extension on the iframe access the parent window object)
function syncOptions() {
var iframe;
if ( !syncing ) {
iframe = $( '<iframe id="' + id.syncIframe + '" width="1" height="1" style="position:absolute;top:-99999px;visibility:hidden;"></iframe>' );
iframe.addEventListener( 'load', syncIframeLoad, false );
iframe.src = getTargetOrigin() + '/search?q=cache%3A';
body.appendChild( iframe );
syncing = true;
// if the iframe takes too long to load, force cleanup
syncCleanup.timeout = window.setTimeout( syncCleanup, 10000 );
$( '#' + id.syncLink )[ 0 ].style.display = 'none';
$( '#' + id.syncing )[ 0 ].style.display = 'inline';
$( '#' + id.syncDone )[ 0 ].style.display = 'none';
}
}
function syncIframeLoad() {
// setTimeout in case our (greasemonkey) script runs after onload
window.setTimeout( receivedMessage, 1000 );
}
function syncCleanup( success ) {
var iframe, after;
if ( syncing ) {
iframe = document.getElementById( id.syncIframe );
iframe.removeEventListener( 'load', syncIframeLoad, false );
body.removeChild( iframe );
syncing = false;
after = function() {
$( '#' + id.syncLink )[ 0 ].style.display = 'inline';
$( '#' + id.syncing )[ 0 ].style.display = 'none';
$( '#' + id.syncDone )[ 0 ].style.display = 'none';
};
if ( success ) {
$( '#' + id.syncDone )[ 0 ].style.display = 'inline';
window.setTimeout( after, 2000 );
} else {
after();
}
}
window.clearTimeout( syncCleanup.timeout );
}
function receivedMessage( e ) {
var me = arguments.callee,
parent = me.parent,
iframe = me.iframe,
data;
if ( !e ) { // init sync
if ( !parent && !iframe ) {
parent = me.parent = getToken();
postToIframe( {
type: 'init',
parent: parent
} );
}
} else if ( e.origin === getTargetOrigin() ) {
data = window.JSON.parse( e.data ) || {};
switch ( data.type ) {
case 'init': // received init from parent, acknowledge
if ( !parent && !iframe ) {
parent = me.parent = data.parent;
iframe = me.iframe = getToken();
postToParent( {
type: 'init-ack',
parent: parent,
iframe: iframe
} );
}
break;
case 'init-ack': // init complete, send options
if ( data.parent === parent && !iframe ) {
iframe = me.iframe = data.iframe;
postToIframe( {
type: 'sync',
parent: parent,
iframe: iframe,
options: $.extend( {}, options )
} );
}
break;
case 'sync': // received options from parent
if ( data.parent === parent && data.iframe === iframe ) {
$.extend( options, data.options );
saveOptions( options );
postToParent( {
type: 'sync-ack',
parent: parent,
iframe: iframe
} );
me.parent = me.iframe = true;
}
break;
case 'sync-ack': // sync complete
if ( data.parent === parent && data.iframe === iframe ) {
syncCleanup( true );
me.parent = me.iframe = null;
}
break;
}
}
}
function postToIframe( data ) {
execScript( [
'document.getElementById(\'', id.syncIframe, '\')',
'.contentWindow',
'.postMessage(\'', window.JSON.stringify( data ).replace( /\\/g, '\\\\' ).replace( /'/g, '\\\'' ), '\', \'', getTargetOrigin(), '\');'
].join( '' ) );
}
function postToParent( data ) {
execScript( [
'if ( parent !== self ) {',
'parent.postMessage(\'', window.JSON.stringify( data ).replace( /\\/g, '\\\\' ).replace( /'/g, '\\\'' ), '\', \'', getTargetOrigin(), '\');',
'}'
].join( '' ) );
}
function getTargetOrigin() {
var me = arguments.callee;
if ( !me.cached ) {
me.cached = ( isHttp ? 'https:' : 'http:' ) + '//' + window.location.host;
}
return me.cached;
}
/*
* event handlers
*/
// show / hide options panel
function optionsLinkClick( e ) {
var panel = $( '#' + id.options )[ 0 ];
if ( e ) {
e.preventDefault();
}
if ( panel.style.display === 'none' ) {
this.innerHTML = strings.hideOptions;
panel.style.display = 'block';
} else {
this.innerHTML = strings.showOptions;
panel.style.display = 'none';
}
}
function redirectPageLinksClick() {
var redirect = this.checked;
options.redirectPageLinks = redirect;
saveOptions( options );
updateLinkHrefs( links, options );
setCacheLinkVisibility( !redirect );
$( '#' + id.message )[ 0 ].innerHTML = redirect ?
strings.redirectLinkExplanation :
strings.cacheLinkExplanation.replace( /%s/g, '<a href="" id="' + id.exampleCacheLink + '" class="' + id.cacheLink + '" ' + getInlineStyle( 'a', css.cacheLink ) + '>' + options.cacheLinkText + '</a>' );
}
function useHttpsClick() {
options.useHttps = this.checked;
saveOptions( options );
updateLinkHrefs( links, options );
}
function cacheLinkTextChange() {
this.value = $.trim( this.value );
if ( !this.value ) {
this.value = defaultOptions.cacheLinkText;
}
options.cacheLinkText = this.value;
saveOptions( options );
updateCacheLinkText( links, options.cacheLinkText );
}
function cacheLinkBackgroundColorChange() {
this.value = $.trim( this.value );
if ( !this.value ) {
this.value = defaultOptions.cacheLinkBackgroundColor;
}
options.cacheLinkBackgroundColor = this.value;
saveOptions( options );
setCacheLinkColors();
}
function cacheLinkTextColorChange() {
this.value = $.trim( this.value );
if ( !this.value ) {
this.value = defaultOptions.cacheLinkTextColor;
}
options.cacheLinkTextColor = this.value;
saveOptions( options );
setCacheLinkColors();
}
function syncLinkClick( e ) {
e.preventDefault();
syncOptions();
}
// show / hide about panel
function aboutLinkClick( e ) {
var panel = $( '#' + id.about )[ 0 ];
if ( e ) {
e.preventDefault();
}
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
}
// initiate check for update
function checkLinkClick( e ) {
e.preventDefault();
if ( options.canCheckForUpdate && !checking ) {
checkForUpdate( options );
}
}
/*
* misc
*/
// returns the current epoch time
function now() { return ( new Date() ).getTime(); }
// returns a unique token string
function getToken() { return ( Math.random() + '' ).replace( /\D/g, '' ); }
// prepends 'googleCache' and appends a unique token to each id
function generateIds( list ) {
var token = getToken(), ids = {};
$.each( list, function( i, name ) { ids[ name ] = 'googleCache' + name.charAt( 0 ).toUpperCase() + name.substring( 1 ) + token; } );
return ids;
}
// runs the given script by appending a script element to the page
function execScript( code ) {
var script = $( '<script></script>' );
script.appendChild( document.createTextNode( code ) );
body.appendChild( script );
window.setTimeout( function() { body.removeChild( script ); }, 500 ); // not sure we need to wait, but doesn't hurt?
}
})( window, document, document.getElementsByTagName( 'head' )[ 0 ], document.body );