// ==UserScript==
// @name				Ultimate Search Highlighter
// @namespace		http://my.opera.com/community/forums/findpost.pl?id=1648805
// @version			1.56
// @description	Highlights search terms on page, with summaries of results, and search engine integration.
// @download		http://files.myopera.com/Stoen/uhb/highlighter.js
// @include			*
// ==/UserScript==

if( !window.opera ) {
	window.opera = {
		isFF: true,
		version: function() { return 10.5; },
		postError: console.log,
		addEventListener: function(a,b,c) {
			window.addEventListener(a.slice(a.indexOf('.')+1),b,c);
		},
		removeEventListener: function(a,b,c) {
			window.removeEventListener(a.slice(a.indexOf('.')+1),b,c);
		}
	};
}
(opera.USH = new function() {
	var preferences = {
		runOnLoad: /*@runOnLoad: Run script on page load based on search engine referrer@bool@*/true/*@*/,
		runOnKeyPress:  /*@runOnKeyPress: Run script on any key press@bool@*/false/*@*/,
		autoHideDelay: /*@autoHideDelay: Time in ms after which the toolbar will hide if not in use. 0 to disable@int@*/2000/*@*/,
		highlightOnLoad: /*@highlightOnLoad: Highlight page automatically or just show toolbar@bool@*/true/*@*/,
		toolbarHiddenOnLoad: /*@toolbarHiddenOnLoad: Toolbar is initially hidden@bool@*/false/*@*/,
		toolbarAtBottom: /*@toolbarAtBottom: Show the toolbar at the bottom of the screen@bool@*/false/*@*/,
		toolbarOverText: /*@toolbarOverText: Show the toolbar over text@bool@*/true/*@*/,
		useStopwords: /*@useStopwords: Highlight words ignored by search engines. Only used for search engine highlights@bool@*/false/*@*/,
		useCookies: /*@useCookies: Use cookies to store searches@bool@*/true/*@*/,
		useDOMStorage: /*@useDOMStorage: Use HTML5 localStorage instead of cookies to store searches@bool@*/true/*@*/,
		enableSearchHistory: /*@enableSearchHistory: Use scriptStorage to store a history of previous searches@bool@*/true/*@*/,
		hideColour: /*@hideColour: Hide coloured highlights for search terms@bool@*/false/*@*/,
		embedStyle: /*@embedStyle: Embed CSS via javascript. False if using external CSS@bool@*/true/*@*/,
		checkDocChanges: /*@checkDocChanges: Re-highlights when scripts dynamically change document content@bool@*/true/*@*/,
		inputDelay: /*@inputDelay: Time in ms after which text typed into the toolbar will be used for highlighting. 0 to disable@int@*/300/*@*/,
		keyShortcuts: [
			['run',				/*@runKey: Keyboard shortcut to run script@string@*/'/?'/*@*/,
										/*@runKey+Optional modifier@bool@*/false/*@*/,
										/*@runKey+Ctrl@bool@*/true/*@*/,
										/*@runKey+Shift@bool@*/false/*@*/],
			['enable',		/*@enableKey: Keyboard shortcut toggle. Enables or disables the following shortcuts@string@*/'`~'/*@*/,
										/*@enableKey+Optional modifier@bool@*/true/*@*/,
										/*@enableKey+Ctrl@bool@*/false/*@*/,
										/*@enableKey+Shift@bool@*/false/*@*/],
			['mOver',			/*@mOverKey: Keyboard shortcut to toggle toolbar visibility@string@*/'9('/*@*/,
										/*@mOverKey+Optional modifier@bool@*/true/*@*/,
										/*@mOverKey+Ctrl@bool@*/false/*@*/,
										/*@mOverKey+Shift@bool@*/false/*@*/],
			['optsBttn',	/*@optsBttnKey: Keyboard shortcut for the 'Options' button@string@*/'0)'/*@*/,
										/*@optsBttnKey+Optional modifier@bool@*/true/*@*/,
										/*@optsBttnKey+Ctrl@bool@*/false/*@*/,
										/*@optsBttnKey+Shift@bool@*/false/*@*/],
			['newBttn',		/*@newBttnKey: Keyboard shortcut for the 'New' button@string@*/'-_'/*@*/,
										/*@newBttnKey+Optional modifier@bool@*/true/*@*/,
										/*@newBttnKey+Ctrl@bool@*/false/*@*/,
										/*@newBttnKey+Shift@bool@*/false/*@*/],
			['hideBttn',	/*@toggleBttnKey: Keyboard shortcut for the 'Toggle' button@string@*/'=+'/*@*/,
										/*@hideBttnKey+Optional modifier@bool@*/true/*@*/,
										/*@hideBttnKey+Ctrl@bool@*/false/*@*/,
										/*@hideBttnKey+Shift@bool@*/false/*@*/],
			['closeBttn',	/*@closeBttnKey: Keyboard shortcut for the 'Close' button@string@*/'\\|'/*@*/,
										/*@closeBttnKey+Optional modifier@bool@*/true/*@*/,
										/*@closeBttnKey+Ctrl@bool@*/false/*@*/,
										/*@closeBttnKey+Shift@bool@*/false/*@*/],
			[0,	/*@term1Key: Keyboard shortcut for search term 1@string@*/'1!'/*@*/,true],
			[1,	/*@term2Key: Keyboard shortcut for search term 2@string@*/'2@'/*@*/,true],
			[2,	/*@term3Key: Keyboard shortcut for search term 3@string@*/'3#'/*@*/,true],
			[3,	/*@term4Key: Keyboard shortcut for search term 4@string@*/'4$'/*@*/,true],
			[4,	/*@term5Key: Keyboard shortcut for search term 5@string@*/'5%'/*@*/,true],
			[5,	/*@term6Key: Keyboard shortcut for search term 6@string@*/'6^'/*@*/,true],
			[6,	/*@term7Key: Keyboard shortcut for search term 7@string@*/'7&'/*@*/,true],
			[7,	/*@term8Key: Keyboard shortcut for search term 8@string@*/'8*'/*@*/,true]
		],
		usePunctuation: /*@usePunctuation: Allow matching of search terms regardless of punctuation@bool@*/false/*@*/,
		wholeWordsOnly: /*@wholeWordsOnly: Only highlight whole words@bool@*/false/*@*/,
		matchCase: /*@matchCase: Highlights are case sensitive@bool@*/false/*@*/,
		useRegExp: /*@useRegExp: Search using a Regular Expression@bool@*/false/*@*/
	},
	colours = ['#ffff66','#A0FFFF','#99ff99','#ff9999','#ff66ff','#FF7F50','#00FF00','#7FFF00','#00BFFF','#FF00FF','#FFD700','#CD5C5C','#C0C0C0','#B0C4DE','#808000','#FFA500','#ADD8E6'],
	searchEngines = [null
		,[1,,,'#USH:(.+)$']
		,[2,'http://\\w+\\.+google\\.[^.]{2,3}(?:\\.[^.]{2})?/search\\?.*',true,'[&?]q=([^&]+)']
		,[2,'\\w+\\.bing\\.com',,'[&?]q=([^&]+)']
		,[2,'search\\.yahoo\\.com',,'[&?]p=([^&]+)']
		,[2,'\\w+\\.wikipedia\\.org/wiki/Special:Search',,'[&?]search=([^&]+)']
		,[2,'(?:www\\.)??ask\\.com',,'[&?]q=([^&]+)']
		,[2,'http://my\\.opera\\.com/community/forums/search\\.dml.*',true,'[&?]term=([^&]+)']
		,[4,,,'UserJS-USH=(.*?)(?:;|$)']
	],
	strings = {
		_opts: 'Options',
		_new: 'New highlight',
		_hide: 'Toggle highlights',
		_close: 'Close',
		_goto: 'Goto next instance of',
		_gotoPrev: 'Goto previous instance of',
		_nfound: 'not found',
		_error: 'Ultimate Search Highlighter:\nFailed to create RegExp. Check syntax\n',
		_usePunctuation: 'Match punctuation',
		_wholeWordsOnly: 'Match whole words only',
		_matchCase: 'Match case',
		_useRegExp: 'Match as RegExp',
		_clearHistory: 'Clear search history'
	},
	evenes = opera.version() >= 10.5,
	frameIndex = 0,
	frames = [null],
	iID = setTimeout(function(){},0),
	merlin = opera.version() < 9.5,
	query = null,
	running = 0,
	USH = this,
	highlight = {
		add: function(e) {
			if( !results.terms ) { return; }

			var excElems = ['UserJS-USH-toolbar','UserJS-USH-highlight','head','applet','object','embed','param','script','noscript','style','frameset','frame','iframe','textarea','input','option','select','img','map'],
					textNodes = document.evaluate('.//text()[normalize-space() and not(ancestor::text() or ancestor::*[contains(" '+excElems.join(' ')+' ",concat(" ",local-name()," "))])]',e&&e.srcElement||document,null,7,null),
					hFind, hElem, i, k, textNode, term;

			if( !(i = textNodes.snapshotLength) ) { return; }
			toolbar.bar.setAttribute('busy','');

			(hElem = document.createElementNS(resolver.xhtmlNS,'UserJS-USH-highlight')).tabIndex = 0;
			hElem.setAttribute('iID',iID);
			hElem.setAttribute(preferences.hideColour?'off':'on','');

			while( i-- ) {
				if( (textNode = textNodes.snapshotItem(i)).nodeType != 3 ) { continue; }
				while(hFind = results.regExp.exec(textNode.data)) {
					if( !hFind[0] ) { break; }
					for( k = 1; !hFind[k++]; );
					term = results.terms[k-=2];

					(hElem = hElem.cloneNode(false)).style.background = term.colour;
					hElem.setAttribute('term',k);

					textNode = textNode.splitText(hFind.index+hFind[0].length-hFind[k+1].length);
					textNode.deleteData(0,(hElem.textContent=hFind[k+1]).length);
					textNode.parentNode.insertBefore(hElem,textNode);
				}
			}
			toolbar.bar.removeAttribute('busy');
		},
		get: function(expr,el) {
			if( (el = el||document).querySelectorAll ) {
				el = el.querySelectorAll('UserJS-USH-highlight'+expr.replace(/@/g,''));
				el.snapshotLength = el.length;
				el.snapshotItem = function(i) { return this[i]; }
				return el;
			}
			return document.evaluate('.//*[local-name()="UserJS-USH-highlight"]'+expr,el,null,7,null);
		},
		remove: function(toggle,idx) {
			var i, j, node, nodes = this.get('[@iID="'+iID+'"][@term'+(idx!==undefined?'="'+idx+'"]':']'));

			i = nodes.snapshotLength; while( i-- ) {
				node = nodes.snapshotItem(i);
				if( toggle ) { node.setAttribute((j = node.hasAttribute('on'))?'off':'on',''); node.removeAttribute(j?'on':'off'); }
				else { j = node.parentNode; j.replaceChild(node.firstChild,node); j.normalize(); }
			}
		}
	},
	mutation = {
		timer: null,
		types: [
			['DOMNodeInsertedIntoDocument',true],
			['DOMNodeRemovedFromDocument',true],
			['DOMCharacterDataModified',false]
		],
		handlers: {
			DOMNodeInsertedIntoDocument: [highlight.add],
			DOMNodeRemovedFromDocument: [],
			DOMCharacterDataModified: [highlight.add]
		},
		handleEvent: function(e) {
			clearTimeout(this.timer);
			var i, handler;
			USH.running(1);
			for( i = 0; handler = this.handlers[e.type][i++]; ) {
				if( handler instanceof Function || (handler = handler.handleEvent) instanceof Function ) { handler(e); }
			}
			this.timer = setTimeout(function(el) { USH.running(1); toolbar.update(); USH.running(0); },500);
			USH.running(0);
		}
	},
	resolver = {
		xhtmlNS: 'http://www.w3.org/1999/xhtml',
		lookupNamespaceURI: function() { return this.xhtmlNS; }
	},
	results = {
		regExp: null,
		terms: null,
		timer: null,
		clear: function() { clearTimeout(this.timer); this.terms = this.regExp = null; },
		handleEvent: function(e) {
			var	el = e.target, term = this.terms[el.idx], d, t = toolbar, o, x, xT, y, yT;

			if( !term || !term.total ) { return }
			clearTimeout(this.timer);

			if( e.ctrlKey ) { if( --term.current < 0 ) { term.current = term.total-1; } }
			else if( ++term.current >= term.total ) { term.current = 0; }
			USH.running(1); el.childNodes[1].data = term.text+' ['+(Math.pow(10,(term.total+'').length)+term.current+1+'').substring(1)+'/'+term.total+'] '; USH.running(0);

			if( !(el = highlight.get('[@iID="'+iID+'"][@term="'+el.idx+'"]').snapshotItem(term.current)) ) { return; }
			t.mOver.className = t.bar.className = 'UserJS-USH-hide';
			d = getDim(el);
			t.mOver.className = t.bar.className = '';
			if( !d.visible ) { return (e.run = term.current||!e.run)?this.handleEvent(e):0; }

			this.timer = setTimeout(function(el) { var r = document.createRange(); r.selectNodeContents(el); getSelection().addRange(r); results.timer = null; },500,el);
			el.focus();

			el.scrollIntoView(!preferences.toolbarAtBottom);
			d = getDim(el); o = getDim(t.input).height; t = getDim(t.bar);
			x = d.left-o; xT = t.left; y = d.bottom+o; yT = t.visible?t.top:0;
			scrollBy(0,-1);
			scrollBy((x<xT || (x=d.right+o)>(xT=t.right))?x-xT:0,1+(preferences.toolbarAtBottom?(y>yT?y-yT:0):((y=d.top-o)<(yT=t.visible?t.bottom:0)?y-yT:0)));
		},
		init: function(qStr) {
			if( qStr === null ) { return; }

			var i, j, k, qArr, term, terms = [], tmp = [],
			punc = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/g,
			wb = (preferences.wholeWordsOnly&&/[^\x20-\x7e]/.test(qStr))?/[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\xa9\xab-\xb4\xb6-\xb9\xbb-\xbf\xd7\xf7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02ED\u02EF-\u02FF\u0374\u0375\u037E-\u0385\u0387\u03F6\u0482\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3-\u060F\u061B-\u061F\u066A-\u066D\u06D4\u06DD\u06E9\u06FD\u06FE\u0700-\u070F\u07F6-\u07F9\u0964\u0965\u0970\u09F2-\u09FA\u0AF1\u0B70\u0BF0-\u0BFA\u0CF1\u0CF2\u0DF4\u0E3F\u0E4F\u0E5A\u0E5B\u0F01-\u0F17\u0F1A-\u0F1F\u0F2A-\u0F34\u0F36\u0F38\u0F3A-\u0F3D\u0F85\u0FBE-\u0FC5\u0FC7-\u0FD1\u104A-\u104F\u10FB\u1360-\u137C\u1390-\u1399\u166D\u166E\u1680\u169B\u169C\u16EB-\u16F0\u1735\u1736\u17B4\u17B5\u17D4-\u17D6\u17D8-\u17DB\u17F0-\u180A\u180E\u1940-\u1945\u19DE-\u19FF\u1A1E\u1A1F\u1B5A-\u1B6A\u1B74-\u1B7C\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20B5\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u2153-\u2182\u2190-\u2B23\u2CE5-\u2CFF\u2E00-\u3004\u3007-\u3029\u3030\u3036-\u303A\u303D-\u303F\u309B\u309C\u30A0\u30FB\u3190-\u319F\u31C0-\u31CF\u3200-\u33FF\u4DC0-\u4DFF\uA490-\uA716\uA720\uA721\uA828-\uA82B\uA874-\uA877\uD800-\uF8FF\uFB29\uFD3E\uFD3F\uFDFC\uFDFD\uFE10-\uFE19\uFE30-\uFE6B\uFEFF-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFFD]/.source:'\\b';

			if( !qStr.indexOf('USHRegExp ') ) { preferences.useRegExp = true; qStr = qStr.substring(10); }
			if( preferences.useRegExp ) { qArr = [,,qStr]; }
			else {
				if( searchEngines[0]&2 ) {
					qStr = qStr.replace(/\++?(\+)?/g,' $1');
					if( !preferences.useStopwords ) { qStr = qStr.replace(/(?:^|\s)(?:intext:|(?:filetype|site|related|info|daterange|link|inurl|inanchor|intitle):\S*|(?:I|a|about|an|are|as|at|be|by|com|for|from|how|in|is|it|of|on|or|that|the|this|to|was|what|when|where|who|will|with|and|the|www)(?=$|\s))/gi,' '); }
				}
				qArr = qStr.split(/([\s+|\-]*)"([^"]*)"/);
				for( i = 0; (term = qArr[i]) != null; i += 3 ) {
					if( !term ) { qArr[i] = 1; continue; }
					term = (' '+term).split(/([\s+|.][\s+|.\-]*)/);
					for( j = 0, k = (term.length-1)/2; j < k; j++ ) { qArr.splice(i+3*j,0,0,term[2*j+1],term[2*j+2]); }
					qArr[i+=3*j] = 1;
				}
			}
			for( i = 2, j = 0; (term = qArr[i]) != null; i += 3 ) {
				if( !term || terms['_'+term] || (/-$/).test(qArr[i-1]) ) { continue; }
				terms[j] = preferences.useRegExp?term:term.replace(punc,qArr[i-2]||preferences.usePunctuation||term.length<2?'\\$&':punc.source+'?');
				terms['_'+term] = true;
				tmp[j] = { text: qArr[i-2]?'"'+term+'"':term, colour: colours[j++%colours.length], total: 0, current: -1 };
			}

			term = (!preferences.useRegExp && preferences.wholeWordsOnly?'(?:^|'+wb+')(?:('+terms.join(')|(')+'))(?='+wb+'|$)':'('+terms.join(')|(')+')');
			try {
				this.regExp = new RegExp(term,preferences.matchCase?'':'i');
				query = qStr;
				this.terms = tmp.length?tmp:null;
				if( searchEngines[0]&1 ) { return; }
				saveVal('query',qStr);
				if( !toolbar.iHandler.timer ) { searchData.historyAdd(qStr); }
			}
			catch(e) { opera.postError(strings._error+term,e); }
		}
	},
	searchData = {
		hash: location.hash,
		referrer: document.referrer,
		storage: (evenes && localStorage.getItem('UserJS-USH'))||document.cookie,
		history: null,
		assign: function(data) {
			var i, key, keys = ['hash','referrer','storage'], sE, rE = new RegExp();
			if( data && (data = data.split('|')) ) {
				for( i = 0; key = keys[i]; ) { this[key] = data[i++]||this[key]; }
			}
			for( i = 1; sE = searchEngines[i++]; ) {
				if( sE[1] && (rE.compile('^'+sE[1]+'$')||rE).test(sE[2]?location:location.hostname) ) {
					this.referrer = this.storage = '';
					if( searchEngines[0] = sE[0]&8 ) { query = sE[3]; return; }
					break;
				}
				switch( searchEngines[0] = sE[0] ) {
					case 1:
						data = this.hash;
						break;
					case preferences.runOnLoad&&2:
						if( (data = this.referrer) && !((rE.compile('^'+(sE[2]?'':'https?:\\/\\/')+sE[1])||rE).test(data)) ) { continue; }
						break;
					case (preferences.useDOMStorage||preferences.useCookies)&&4:
						data = this.storage;
				}

				if( !data ) { continue; }
				rE.compile(sE[3]);
				if( data = rE.exec(data) ) {
					query = searchEngines[0]&1?data[1]:decodeURIComponent(data[1]);
					break;
				}
			}
		},
		historyAdd: function(val) {
			if( !this.history || this.history[val] ) { return; }
			toolbar.list.appendChild(new Option()).value = val;
			this.history.setItem(val,0);
		},
		historyDel: function(val) {
			if( !this.history || !(val = document.evaluate('./option[@value="'+val+'"]',toolbar.list,null,9,null).singleNodeValue) ) { return; }
			this.history.removeItem(val.value);
			toolbar.list.removeChild(val);
		},
		historyClear: function() {
			if( !this.history ) { return; }
			toolbar.list.textContent = '';
			this.history.clear();
		},
		toString: function() { return this.hash+'|'+this.referrer+'|'+this.storage; }
	},
	toolbar = {
		enabled: false,
		timer: null,
		visible: false,
		bar: null,
		buttons: [],
		closeBttn: null,
		list: null,
		hideBttn: null,
		input: null,
		mOver: null,
		newBttn: null,
		optsBttn: null,
		optsMenu: null,
		styles: null,
		create: function() {
			var d = document, xhtmlNS = resolver.xhtmlNS, divEl, elem, i, term, terms;

			if( !this.bar ) {
				(divEl = this.bar = d.createElementNS(xhtmlNS,'UserJS-USH-toolbar')).tabIndex = 0;
				divEl.focus = function(oF) { return function() {
					this.className = 'UserJS-USH-hide';
					oF.call(this);
					this.className = '';
				}}(divEl.focus);

				function createButton(id,fn) {
					var el = d.createElementNS(xhtmlNS,'label');
					el.className = 'UserJS-USH-bttn-icon-'+id; el.title = strings['_'+id];
					el.addEventListener('click',fn||function(e){ USH.run(e,this.className); },false);
					return toolbar.bar.appendChild(toolbar[id+'Bttn'] = el);
				}

				function createPref(id,fn) {
					var el = d.createElementNS(xhtmlNS,'label');
					el.appendChild(d.createElementNS(xhtmlNS,'input')).type = 'checkbox';
					el.firstChild.prefIdx = id;
					el.firstChild.checked = preferences[id];
					el.firstChild.addEventListener('change',fn||function(e) { saveVal(this.prefIdx,this.checked); USH.run(e,'UserJS-USH-bttn-icon-new'); },false);
					el.appendChild(d.createTextNode(strings['_'+id]));
					return toolbar.optsMenu.appendChild(el);
				}

				createButton('close');
				createButton('hide');
				createButton('opts',function(e) {
					var active = this.hasAttribute('active');
					this[active?'removeAttribute':'setAttribute']('active','');
					toolbar.optsMenu.className = (active||e.ctrlKey?'UserJS-USH-hide':'');
				});

				(this.input = i = this.bar.appendChild(d.createElementNS(xhtmlNS,'input'))).id = 'UserJS-USH-input';
				i.setAttribute('form','');
				i.addEventListener('input',this.iHandler,false);

				createButton('new');

				(this.bar.appendChild(this.optsMenu = d.createElementNS(xhtmlNS,'optgroup'))).className = 'UserJS-USH-hide';
				createPref('usePunctuation');
				createPref('wholeWordsOnly');
				createPref('matchCase');
				createPref('useRegExp');

				if( searchData.history ) {
					this.input.setAttribute('list',(this.list = this.bar.appendChild(d.createElementNS(xhtmlNS,'datalist'))).id = 'UserJS-USH-list');
					createPref('clearHistory',function() { searchData.historyClear(); this.checked = false; });
					for( i = searchData.history.length; i--; ) { this.list.appendChild(new Option()).value = searchData.history.key(i); }
				}

				(this.mOver = d.createElementNS(xhtmlNS,'UserJS-USH-mouseover')).addEventListener('click',this,false);
				if( !preferences.autoHideDelay ) { preferences.toolbarOverText = false; }
				else {
					divEl.addEventListener('DOMFocusOut',this,false);
					divEl.addEventListener('mouseout',this,false);
					divEl.addEventListener('mouseover',this,false);
					this.mOver.addEventListener('mouseover',this,false);
				}

				if( preferences.embedStyle || !preferences.toolbarOverText ) {
					(this.styles = d.createElementNS(xhtmlNS,"style")).textContent = 'html { position: relative !important; }'+(preferences.embedStyle?'\
					UserJS-USH-mouseover { height: 1em !important; z-index: 99980 !important; }\
					UserJS-USH-toolbar { background: -o-skin("Viewbar Skin") #f2f2ee !important; border: 1px solid #999 !important; z-index: 99990 !important; }\
					UserJS-USH-toolbar[mini=on], UserJS-USH-mouseover[mini=on] { right: auto !important; min-width: 26.5em !important; }\
					UserJS-USH-toolbar[top], UserJS-USH-mouseover[top] { bottom: auto !important; top: -1px !important; }\
					#UserJS-USH-list, .UserJS-USH-hide { display: none !important; }\
					UserJS-USH-toolbar, UserJS-USH-mouseover { display: block !important; position: fixed !important; bottom: -1px !important; left: -1px !important; right: -1px !important; min-width: 100% !important; font: 12px/18px Arial, sans-serif !important; }\
					UserJS-USH-toolbar, UserJS-USH-toolbar *, UserJS-USH-highlight[on] { text-align: left !important; text-indent: 0 !important; margin: 0 !important; padding: 0 !important; min-height: 0 !important; height: auto !important; max-height: none !important; min-width: 0 !important; width: auto !important; max-width: none !important; float: none !important; clear: none !important; color: #000 !important; }\
					UserJS-USH-toolbar * { display: inline-block !important; font: inherit !important; vertical-align: middle !important; border: none !important; box-sizing: border-box !important; margin: .2em !important; text-shadow: #FFF 0 0 5px !important; }\
					UserJS-USH-highlight[off] { background: transparent !important; color: inherit !important; }\
					UserJS-USH-highlight:focus { outline: -o-highlight-border !important; }\
					[class^=UserJS-USH-bttn] { background: -o-skin("Addressbar Button Skin") !important; padding: .2em .3em !important; cursor: hand !important; }\
					[class^=UserJS-USH-bttn]:hover { background: -o-skin("Addressbar Button Skin.hover") !important; }\
					[class^=UserJS-USH-bttn]:active, [class^=UserJS-USH-bttn][active] { background: -o-skin("Addressbar Button Skin.pressed") !important; }\
					[class^=UserJS-USH-bttn]>UserJS-USH-icon { width: 1em !important; height: 1em !important; border: 1px #666 solid !important; }\
					[class^=UserJS-USH-bttn]>.UserJS-USH-icon-prev { background: -o-skin("Find Previous") !important; }\
					[class^=UserJS-USH-bttn-icon], [class^=UserJS-USH-bttn-icon]::before { content: "" !important; float: right !important; min-height: 1.5em !important; min-width: 1.5em !important; }\
					.UserJS-USH-bttn-icon-opts::before { background: -o-skin("Panel Info") !important; }\
					.UserJS-USH-bttn-icon-new::before { background: -o-skin("Find") !important; }\
					UserJS-USH-toolbar[busy]>.UserJS-USH-bttn-icon-new::before { background: -o-skin("Thumbnail Busy Image") !important; }\
					.UserJS-USH-bttn-icon-hide::before { background: -o-skin("Caption Restore") !important; }\
					.UserJS-USH-bttn-icon-close::before { background: -o-skin("Caption Close") !important; }\
					.UserJS-USH-bttn-icon-opts, .UserJS-USH-bttn-icon-new { float: none !important; }\
					#UserJS-USH-input { min-width: 16em !important; background: -o-skin("Edit Skin") !important; padding: .3em !important; }\
					UserJS-USH-toolbar>optgroup { position: absolute !important; bottom: 100% !important; background: inherit !important; border: 1px solid #999 !important; margin: 0 .2em !important; }\
					UserJS-USH-toolbar>optgroup>* { display: block !important; }\
					UserJS-USH-toolbar[top]>optgroup { bottom: auto !important; top: 100% !important; }':'');
				}
			}

			this.visible = false;
			if( this.enabled ) { return; }
			if( this.styles ) { d.documentElement.appendChild(this.styles); }
			if( !preferences.toolbarAtBottom ) {
				this.bar.setAttribute('top','');
				this.mOver.setAttribute('top','');
			}
			if( preferences.toolbarOverText ) {
				this.bar.setAttribute('mini','on');
				this.mOver.setAttribute('mini','on');
			}
			d.documentElement.appendChild(this.mOver);
			d.documentElement.appendChild(this.bar);
			if( !preferences.toolbarOverText ) { self.addEventListener('resize',this,false); }
			this.enabled = true;
		},
		handleEvent: function(e,state) {
			clearTimeout(this.timer);
			if( e ) { switch( e.type||e ) {
				case 'click':	this.bar.focus(); break;
				case 'DOMFocusOut':
					if( e.target == this.input && !this.iHandler.timer ) {
						searchData.historyAdd(e.target.value);
						e.target.removeAttribute('pattern');
					}
					if( results.timer || this.bar.contains(document.activeElement||document.body) ) { return; }
				case 'mouseout':
					return this.timer = (document.activeElement==this.input?null:setTimeout(function(t) { t.handleEvent(null,false); },preferences.autoHideDelay,this));
				case 'resize':
					if( !this.styles.parentNode ) { USH.running(1); document.documentElement.appendChild(this.styles); USH.running(0); }
					this.styles.sheet.cssRules[0].style[e = 'margin'+(preferences.toolbarAtBottom?'Bottom':'Top')] = '';
					return this.visible && (this.styles.sheet.cssRules[0].style[e] = parseInt(getComputedStyle(document.documentElement,'')[e])+getDim(this.bar).height+'px !important');
				default: state = true;
			}}
			this.bar.style.visibility = (state = (state===undefined?!this.visible:state)||this.optsBttn.hasAttribute('active'))?'':'hidden';
			if( this.visible != (this.visible = state) && !preferences.toolbarOverText ) { this.handleEvent('resize'); }
		},
		iHandler: {
			timer: null,
			oVal: '',
			triggered: false,
			handleEvent: function(e) {
				this.timer = clearTimeout(this.timer);
				var i = e.target, oVal = this.oVal, val = i.value, valU = val.toUpperCase(), valL = val.toLowerCase(), opt;

				if( oVal == (this.oVal=val) || toolbar.list && this.triggered ) { return this.triggered = false; }
				this.timer = preferences.inputDelay&&setTimeout(function(i,val) {
					i = document.activeElement==i;
					USH.run(val,'new');
					i&&USH.run(null,'edit');
					toolbar.iHandler.timer = null;
				},preferences.inputDelay,i,val);

				if( !toolbar.list ) { return; }
				i.setAttribute('pattern',valL.replace(/[a-z]/g,function(l,i){ return '['+l+valU[i]+']'; })+'.*');
				if( val.length > oVal.length && (opt = document.evaluate('./option[starts-with(translate(@value,"'+valU+'","'+valL+'"),"'+valL+'")]',toolbar.list,null,9,null).singleNodeValue) ) {
					if( valL != (opt=opt.value).toLowerCase() ) {
						i.value += opt.substring(val.length);
						i.setSelectionRange(val.length,opt.length);
					}
				}
			}
		},
		remove: function() {
			clearTimeout(this.timer);
			clearTimeout(this.iHandler.timer);
			document.removeEventListener('resize',this,false);
			if( this.styles ) { this.styles.parentNode.removeChild(this.styles); }
			this.bar.parentNode.removeChild(this.bar);
			this.mOver.parentNode.removeChild(this.mOver);
			this.update();
			this.visible = this.enabled = false;
		},
		update: function(delay) {
			var bttn, icon, i, term, terms, total, text;

			if( !this.enabled ) { return; }

			this.input.value = this.input.value||query||'';
			i = (terms=this.bar.children).length; while( i > (this.list?7:6) ) { this.bar.removeChild(terms[--i]); }
			i = (terms=this.optsMenu.children).length; while( i ) { (term = terms[--i].firstChild).checked = preferences[term.prefIdx]; }

			if( preferences.toolbarOverText ) {
				this.bar.setAttribute('mini',results.terms?'':'on');
				this.mOver.setAttribute('mini',results.terms?'':'on');
			}

			if( !results.terms ) { return; }
			(bttn = document.createElementNS(resolver.xhtmlNS,'label')).className = 'UserJS-USH-bttn';
			icon = document.createElementNS(resolver.xhtmlNS,'UserJS-USH-icon');

			for( i = 0; term = results.terms[i]; i++ ) {
				(this.buttons[i] = bttn = bttn.cloneNode(false)).idx = i;
				text = bttn.textContent = term.text;
				total = highlight.get('[@iID="'+iID+'"][@term="'+i+'"]').snapshotLength;
				bttn.title = total?strings._goto+' "'+text+'"':'"'+text+'" '+strings._nfound;
				if( total ) {
					if( term.current >= total ) { term.current -= term.total - total; }
					bttn.addEventListener('click',results,false);
					bttn.textContent += ' ['+(Math.pow(10,(total+'').length)+term.current+1+'').substring(1)+'/'+total+'] ';
					(icon = bttn.appendChild(icon.cloneNode(false))).style.background = term.colour+' !important';
					icon.className = '';
					icon.addEventListener('click',function() { highlight.remove(true,this.parentNode.idx); },false);
					icon.title = strings._hide;
					(icon = bttn.insertBefore(icon.cloneNode(false),bttn.firstChild)).style = '';
					icon.className = 'UserJS-USH-icon-prev';
					icon.addEventListener('click',function() { results.handleEvent({target: this.parentNode, ctrlKey: true}); },false);
					icon.title = strings._gotoPrev+' "'+text+'"';
				}
				term.total = total;
				this.bar.appendChild(bttn);
			}
		}
	},
	exit = function() {
		USH.running(1);
		highlight.remove();
		saveVal('query',query='');
		results.clear();
		toolbar.remove();
		USH.running(0);
	},
	getDim = function(el) {
		var t1 = -self.pageYOffset, t = t1, l1 = -self.pageXOffset, l = l1, d, oEl = el, h = el.offsetHeight, w = el.offsetWidth;
		if( el.getBoundingClientRect ) { d = el.getBoundingClientRect(); d.height = h; d.width = w; }
		else {
			while( el ) { t += el.offsetTop; l += el.offsetLeft; el = el.offsetParent; }
			d = {left:l, top:t, right:w+l, bottom:h+t, height:h, width:w};
		}
		d.visible = h && w && d.bottom>t1 && d.right>l1 && (oEl.contains(el=document.elementFromPoint((evenes?0:-l1)+(d.left+d.right)/2,(evenes?0:-t1)+(d.top+d.bottom)/2)) || el.contains&&el.contains(oEl));
		return d;
	},
	run = function() {
		USH.running(1);
		toolbar.create();
		if( !searchEngines[0] || preferences.highlightOnLoad ) {
			results.init(query);
			highlight.remove();
			highlight.add();
		}
		toolbar.update();
		toolbar.handleEvent(self==top?'click':null,!preferences.autoHideDelay||!searchEngines[0]||!preferences.toolbarHiddenOnLoad);
		searchEngines[0] = 0;
		USH.running(0);
	},
	saveVal = function(key,val) {
		preferences[key] = val;
		if( key != 'query' ) { return; }
		if( preferences.useDOMStorage && evenes ) {
			val?localStorage.setItem('UserJS-USH','UserJS-USH='+val):localStorage.removeItem('UserJS-USH');
		}
		else if( preferences.useCookies ) {
			document.cookie = 'UserJS-USH='+encodeURIComponent(val)+';path=/;'+(val?'':'expires='+new Date(0).toGMTString());
		}
	};

	this.init = function(prefs,store) {
		if( !(document.documentElement instanceof HTMLHtmlElement) && !opera.isFF ) { return; }
		delete this.init;
		delete opera.USHprefs;

		if( prefs instanceof Function ) {
			preferences = prefs('preferences');
			colours = prefs('colours');
			searchEngines = prefs('searchEngines');
			strings = prefs('strings');
		}

		opera.addEventListener('BeforeEvent.message',function(oE) {
			var e = oE.event||oE, msg = e.data.toString(), data;
			if( !!msg.indexOf('USH|') ) { return; }
			oE.preventDefault(); e.preventDefault();
			switch( msg = msg.substring(4) )	{
				case 'loadFrame':
					e.source.postMessage('USH|frameIndex|'+frames.length,'*');
					e.source.postMessage('USH|load|'+searchData,'*');
					frames[frames.length] = e.source;
					break;
				case 'frameIndex|'+(data=msg.substring(11)):
					frameIndex = data;
					break;
				case 'load|'+(data=msg.substring(5)):
					searchData.assign(data);
					if( !query && USH.query ) {
						query = USH.query;
						searchEngines[0] = 8;
						delete USH.query;
					}
					if( query && searchEngines[0] ) { run(); }
					break;
				case 'run|'+(data=msg.substring(4)):
					data = data.split('|');
					USH.run(data[0],data[1],data[2]^0);
					break;
			}
		},false);

		opera.addEventListener('BeforeEvent.keypress',({
			enabled: false,
			keys: {},
			event: document.createEvent('UIEvents'),
			init: function() {
				var i, j, key, code;

				for( i = j = 0; key = preferences.keyShortcuts[i++]; j = 0 ) {
					if( key[1] ) {
						while( code = key[1].charCodeAt(j++) ) { this.keys['_'+code] = key[0]; }
						this.keys[key[0]] = (key[2]<<0) + (key[3]<<1) + (key[4]<<2);
					}
				}
				if( this.keys.enable === undefined ) { this.enabled = true; }

				this.event.initEvent('click',true,true);

				return this;
			},
			handleEvent: function(oE) {
				var e = oE.event||oE, el = e.target, key = e.keyCode, bttn = this.keys['_'+key],
						keyChk = (this.keys[bttn]&1 || this.keys[bttn] == (e.ctrlKey<<1) + (e.shiftKey<<2))&&bttn;

				if( keyChk == 'run' ) { return USH.run(getSelection().toString()||null,'newBlank'); }
				if( el == toolbar.input ) {
					toolbar.iHandler.triggered = !e.which;
					switch( key ) {
						case 13: USH.run(e,'UserJS-USH-bttn-icon-new'); break;
						case 27: el.blur(); break;
						case 8:
							if( !e.ctrlKey ) { break; }
							searchData.historyDel(el.value);
							USH.run(el.value = '','newBlank');
					}
				}
				if( el.forms instanceof NodeList ) { return; }
				if( preferences.runOnKeyPress && e.which ) { USH.run(null,'new'); USH.run(null,'edit'); return; }
				if( !toolbar.enabled ) { return; }

				if( keyChk == 'enable' ) { this.enabled = !this.enabled; }
				if( this.enabled && keyChk !== false && (bttn = ((typeof bttn == 'number')?toolbar.buttons:toolbar)[bttn]) ) {
					oE.preventDefault(); e.preventDefault();
					this.event.shiftKey = e.shiftKey; this.event.ctrlKey = e.ctrlKey;
					bttn.dispatchEvent(this.event);
				}
			}
		}).init(),false);

		opera.addEventListener('AfterEvent.DOMContentLoaded',{handleEvent: function(oE) {
			opera.removeEventListener(oE.type,this,false);
			(merlin?top.document:top).postMessage(self==top?'USH|load|':'USH|loadFrame','*');
		}},false);

		if( preferences.checkDocChanges ) {
			this.addEventListener = function(type,handler) {
				USH.running(1); USH.running(0);
				if( mutation.handlers[type] && handler ) { mutation.handlers[type].push(handler); }
			}
		}

		if( preferences.enableSearchHistory && store ) { searchData.history = store; }
	}

	this.run = function(e,action,frame) {
		var i = toolbar.enabled && toolbar.input, f;
		frame = frame|| e&&e.shiftKey;
		switch( action ) {
			case 'UserJS-USH-bttn-icon-close':
				i && exit();
				break;
			case 'UserJS-USH-bttn-icon-hide':
				i && highlight.remove(true);
				break;
			case 'UserJS-USH-bttn-icon-new':
				return i && USH.run(i.value.replace(/^\s*(USHRegExp\s)?\s*/,e&&e.ctrlKey?'USHRegExp ':'$1'),'new',frame);
			case 'newSearch':
				if( !e || e == location ) { return; }
				frame = true;
			case 'newSearchFrame':
				searchEngines[0] = searchEngines[0]||2;
				action = 'newSearchFrame';
			case 'newBlank': case 'new':
				query = e;
				run();
				if( action == 'new' ) { break; }
				(i = toolbar.input).select();
			case 'edit':
				i && i.focus();
				frame = document.body instanceof HTMLFrameSetElement;
		}
		if( !frame ) { return; }
		if( self != top ) { return (merlin?top.document:top).postMessage('USH|run|'+query+'|'+action+'|'+frameIndex,'*'); }
		for( i = 1; f = frames[i]; ) {
			if( i++ !== frame ) { (merlin?f.document:f).postMessage('USH|run|'+query+'|'+action,'*'); }
		}
	}

	this.running = function(val) {
		var i, type;
		if( val === undefined ) { return running; }
		val = val?1:running?2:0;
		if( preferences.checkDocChanges && val ) {
			for( i = 0; type = mutation.types[i++]; ) { document[val&1?'removeEventListener':'addEventListener'](type[0],mutation,type[1]); }
		}
		return running = !!(val&1);
	}
}).init(opera.USHprefs,opera.scriptStorage);