// ==UserScript==
// @name          Middle Click Override for Opera 9.5+
// @version       1.53
// @description   Allows javascript links, buttons, etc. to work with the middle mouse button
//								Original concept by xErath (http://files.myopera.com/xErath/files/middleclick.js)
//								Works best when opera:config#UserPrefs|TargetDestination is 2
// @include       *
// ==/UserScript==

(function(opera){
	var preventJSLocationModification = /*@preventJSLocationModification: window.location modification opens a background window@bool@*/true/*@*/,
			openerHistory = /*@openerHistory: Retains the originating page in the history of a newly opened window@bool@*/false/*@*/,
			onlyLinksOrButton = /*@onlyLinksOrButton: Only run script when clicking links or buttons@bool@*/false/*@*/,
			leftClickInBackground = /*@leftClickInBackground: Open links in background when you click and hold the left mouse button@bool@*/true/*@*/,
			leftClickDelay = /*@leftClickDelay: Required hold time (in ms) for leftClickInBackground setting@int@*/500/*@*/;

	var origLocObject = location, clickContext, nextEvType, oFocus = focus, oBlur = blur, oOpen = open;

	focus = blur = function(){};
	open = function() {
		var loc = arguments[0];
		openerHistory&&(arguments[0] = '');
		clickContext&&(arguments[1] = '_blank');
		var win = oOpen.apply(self,arguments);
		openerHistory&&(win.location = loc);
		clickContext&&oFocus.call(self);
		return win;
	}

	if( preventJSLocationModification ) {
		var newLocObject = {
			get hash() { return origLocObject.hash; },
			set hash(val) { return origLocObject.hash = val; },
			get host() { return origLocObject.host; },
			set host(val) { return open().location.host = val; },
			get hostname() { return origLocObject.hostname; },
			set hostname(val) { return open().location.hostname = val; },
			get href() { return origLocObject.href; },
			set href(val) { return open().location.href = val; },
			get pathname() { return origLocObject.pathname; },
			set pathname(val) { return open().location.pathname = val; },
			get port() { return origLocObject.port; },
			set port(val) { return open().location.port = val; },
			get protocol() { return origLocObject.protocol; },
			set protocol(val) { return open().location.protocol = val; },
			get search() { return origLocObject.search; },
			set search(val) { return open().location.search = val; },
			reload: function(arg) { origLocObject.reload(arg); },
			toString: function() { return origLocObject.toString(); },
			valueOf: function() { return origLocObject.valueOf(); },
			replace: function(loc) { open(loc) },
			assign: function(loc) { open(loc) }};
		function getLoc() { return clickContext?newLocObject:origLocObject; }
		function setLoc(loc) { clickContext?open(loc):origLocObject.assign(loc); return loc; }
		opera.defineMagicVariable('location',getLoc,setLoc);
	}

	opera.addEventListener('BeforeEvent.mousedown',function(ujsEv) {
		var ev = ujsEv.event;
		if( ev.button == 2 || (!leftClickInBackground && !ev.button)  ) { return; }
		if( ev.triggered ) { ujsEv.preventDefault(); return; }
		fixAll();

		var cNode = ev.target.selectSingleNode('ancestor-or-self::*[ ((local-name()="a" or local-name()="area") and @href) or (local-name()="input" and (@type="submit" or @type="image")) or (local-name()="button" and (not(@type) or @type="submit" or @type="button")) ]');
		if( !cNode && (onlyLinksOrButton || !ev.button) ) { return; }

		clickContext = {
			isLeftClick: !ev.button,
			timeStamp: new Date().getTime()
		};
		ev.__defineGetter__('button',function() { return 0; });
		ev.__defineGetter__('which',function() { return 1; });
		opera.addEventListener('BeforeEventListener.'+ev.type,beforeEvL,false);
		opera.addEventListener('AfterEvent.'+ev.type,afterEv,false);

		if( !cNode ) { return }

		if( clickContext.isLink = !(cNode.forms instanceof NodeList) ) {
			if( cNode.protocol && cNode.protocol.toLowerCase() == 'javascript:' ) {
				clickContext.jsCode = cNode.href.substr(11);
			}
			clickContext.href = cNode.getAttribute('href');
			if( !clickContext.isLeftClick ) { cNode.removeAttribute('href'); }
		}
		else {
			cNode = (cNode.hasAttribute('action')||!cNode.form)?cNode:cNode.form;
			cNode.hasAttribute('target')&&(clickContext.target = cNode.getAttribute('target'));
			cNode.setAttribute('target','_blank');
		}
		clickContext.cNode = cNode;
	},false);

	function beforeEv() {
		if( clickContext.isLeftClick && new Date().getTime() - clickContext.timeStamp < leftClickDelay ) { fixAll(); }
	}

	function beforeEvL() {
		if( clickContext.tmpFix = clickContext.isLink ) {
			clickContext.cNode.setAttribute('href',clickContext.href);
		}
	}

	function afterEv(ujsEv) {
		var ev = ujsEv.event;
		fixAll(ev.type);

		if( clickContext.tmpFix ) {
			clickContext.href = clickContext.cNode.getAttribute('href');
			clickContext.tmpFix = clickContext.cNode.removeAttribute('href');
		}

		switch( ev.type ) {
		case 'mousedown':
			opera.addEventListener('BeforeEvent.'+(nextEvType = 'mouseup'),beforeEv,false);
			break;
		case 'mouseup':
			nextEvType = 'click'; break;
		case 'click':
			setTimeout(fixAll,0);
			if( ujsEv.eventCancelled || !clickContext.isLink ) { return; }
			ev.preventDefault();
			if( clickContext.jsCode ) { (new Function(clickContext.jsCode))(); }
			else if( openerHistory ) { open(clickContext.href); }
			else { triggerEvent('mousedown',ev,1); }
			return;
		}

		opera.addEventListener('BeforeEventListener.'+nextEvType,beforeEvL,false);
		opera.addEventListener('AfterEvent.'+nextEvType,afterEv,false);
		if( !clickContext.isLeftClick ) { triggerEvent(nextEvType,ev); }
	}

	function fixAll(evt) {
		opera.removeEventListener('BeforeEvent.'+(evt||nextEvType),beforeEv,false);
		opera.removeEventListener('BeforeEventListener.'+(evt||nextEvType),beforeEvL,false);
		opera.removeEventListener('AfterEvent.'+(evt||nextEvType),afterEv,false);
		if( !clickContext || evt ) { return; }
		if( clickContext.isLink ) { clickContext.cNode.setAttribute('href',clickContext.href); }
		else if( clickContext.cNode ) {
			clickContext.cNode[clickContext.target===undefined?'removeAttribute':'setAttribute']('target',clickContext.target);
			//oBlur.call(self);
		}
		oFocus.call(self);
		clickContext = null;
	}

	function triggerEvent(type,oEvent,button,now) {
		var newEv = document.createEvent('MouseEvents');
		newEv.initMouseEvent(type,true,true,window,1,oEvent.screenX||0,oEvent.screenY||0,oEvent.clientX||0,oEvent.clientY||0,oEvent.ctrlKey||0,oEvent.altKey||0,oEvent.shiftKey||0,oEvent.metaKey||0,button||0,null);
		newEv.triggered = true;
		now?oEvent.target.dispatchEvent(newEv):setTimeout(function(el,ev) { el.dispatchEvent(ev); },0,oEvent.target,newEv);
	}
})(window.opera);