thomasfrank.se

Developer friendly tabbed browsing:
winOpenerFix.js

May 16, 2008

Cue up boomy speaker voice and threatening dark string sounds:
“In the age of tabbed browsing users started to right click links to open them in new tabs – not caring if they killed JavaScript apps and thwarted innocent developers.
And though some browsers could tell what window had spawned another some just couldn’t. Chaos ruled.
Until one man
[start theme music] had a revelation: He would make the window.opener property work again, in all browsers. Armed only with a text editor he fought for a better world where tabbed browsing would no longer hurt anyone. And so the winOpenerFix was born.”

The problem

When you right click a link and choose to open it in a new tab or a new window, it would be nice if the old window would get referenced in the window.opener property (provided the link is within the same domain).
That would give the new window access to the JavaScript and DOM objects from the old window. This happens in IE and Opera, but not in Firefox and Safari.
See this anomoly in action by loading this example in different browsers.

The solution

My solution is based on the following steps:
  1. The main JavaScript adds a hidden iframe with a unique window.name to the page.
  2. The script also allows you to modify all the links on the page so that they react differently when you right click them.
  3. A small html page will load instead of the real url when you open a link in a new tab or window. This page runs a few lines of script that will give this new window a unique window.name. After that it calls the main script on the original page and announces its name. This is done by reloading the content in the hidden iframe.
  4. The main JavaScript will then load the correct url in the new window.

Try it out here or download the solution with examples as a zip file (4 kB).

How to use


This is still a bit experimental so let me know if you run into any problems using it or if you have got a better solution to the problem. Thanks to Morten Barklund for suggesting part of this solution.

The code in a commented version

Here's the actual code in a commented version. The main script:
winOpenerFix = {
	/* 	
		htmlDoc:
		path to our html "helper page"
	*/
	htmlDoc:"winOpenerFix.html",
	/*
		updateLinks() 
		method to call onload/after links are created
	*/
	updateLinks:function(){	
		/* 	
			some browser detection
			(couldn't find a better way to detect if the browser handles
			window.opener correctly in tabbed browsing) 
		*/	
		Opera = (navigator.userAgent.toLowerCase().indexOf("opera")! = -1);
		Safari = (navigator.userAgent.toLowerCase().indexOf("safari")! = -1);
		Explorer = (document.all && (!(Opera || Safari)));
		if(Opera || Explorer){return};
		/*
			run winOpenerFix.init()
			if it hasn't been done before
		*/
		var t = this, l = document.links;
		if(!t.inited){t.init()};
		/*
			go through all links on the page
			add event handlers
			that change their urls on right click (context menu)
			and restore them on mouseover
		*/
		for(var i = 0;i<l.length;i++){
			var f = function(){
				var orgRef = l[i].href;
				var contextmenu = function(){
					this.href = t.htmlDoc+"?"+t.id;
					t.currentLink = orgRef
				};
				var mouseover = function(){
					this.href = orgRef
				};
				if(l[i].winOpenerFixEventsApplied){return};
				t.addEvent(l[i],"contextmenu",contextmenu);
				t.addEvent(l[i],"mouseover",mouseover);
				l[i].winOpenerFixEventsApplied = true
			};
			f()
		}
	},
	/* 
		loadPage()
		gets called after our helper page has opened in
		a new window/tab. Opens the real url in that window.
	*/
	loadPage:function(l){
		if(!l){return};
		open(this.currentLink,"winOpenerFixWinName"+l)
	},
	/*
		addEvent() 
		cross browser event adder
	*/
	addEvent:function(obj, evType, fn, useCapture){	
		if (obj.addEventListener){
    			obj.addEventListener(evType, fn, useCapture);
    			return true;
  		} else if (obj.attachEvent){
   			 var r  =  obj.attachEvent("on"+evType, fn);
   			 return r;
  		}
	}, 
	/*
		 init()
		creates a hidden iframe
		with a unique name
	*/
	init:function(){
		this.id = new Date().getTime();
		var d = document.createElement('div');
		document.body.appendChild(d);
		d.style.display = "none";
		d.innerHTML = '<iframe name = "winOpenerFixIFrame'
                  +this.id+'" src = "'+this.htmlDoc+'"></iframe>';
		this.inited = true
	}
};


And the html helper page:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" 
"http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head>
<script type = "text/javascript">
/*
	extract my query parameter
*/
var l = location.href.split("?");
var myref = l[0];
l = l[1]||"";
/*
	if this document is not in the iframe
	create a unique window.name 
	reload the iframe with this name
	as a query parameter
*/
if(!parent.winOpenerFix){
	var l2 = new Date().getTime();
	window.name = "winOpenerFixWinName"+l2;
	open(myref+"?"+l2,"winOpenerFixIFrame"+l)
}
/*
	if this document is in the iframe
	trigger main script to load the currect url
	in the new window
*/
else {parent.winOpenerFix.loadPage(l)}
</script>
</head>
<body>
</body>
</html>
[comments]