Multiple Popup Windows Workaround PDF Print E-mail
Sunday, 16 May 2010 21:45

JavaScriptPop-up window management poses a challenge in itself, but with an elusive Firefox bug effecting this process, it may seem impossible to do it well. I will present you with an account of my struggle with this issue and the simple solution I found for it. I will demonstrate the problem, the inspiration for the fix, improvements to this early fix and a short discuss of how this can be applied to other areas of web development. 



Problem

In Firefox, if window.open is called before a previous call to this method had a chance to return, multiple windows would appear, even if the same window name is used in all calls. You might ask yourself, when does this really happen? Well, just think of an event handler being fired multiple times. Let this event be an onclick event and lets use the following event handler for it:




<script type="text/javascript" language="JavaScript">
function openPopup() {
    // should only open one popup window even after multiple calls... but does it?
    window.open("http://www.google.com", "popup", "width=500,height=500");
}
</script>
.
.
<a href="javascript:openPopup();" onclick="openPopup();">example</a>
.
.

Click on the example link in Firefox as fast as you can example.
You should see multiple popups opening if you clicked fast enough.


But wait, Mozilla recommends a different way to open a new window. Consider the following code:

function openPopup2() {
	var windowObjectReference = null;
	if(windowObjectReference == null || windowObjectReference.closed) {
		// should only open one popup window even after multiple calls... but does it?
		windowObjectReference = window.open("http://www.google.com", "popup", "width=500,height=500");
	}
	else {
		windowObjectReference.focus(); 
	}
}

But this does not work either. The windowObjectReference remains null even after the initial call to window.open. This is due to the fact that the function does not return immediately like the documentation suggests (the function is described as Asynchronous). More about that in the next section.


The bug you have just witnessed is described by Mozilla here. This bug has been open for 5 years now and is not yet fixed in Firefox 4.0 beta (although the speed ups to the JavaScript engine help mask the problem).  

In IE 7 multiple windows will open, but once the page has been loaded into the popup window, other windows with the same name will be closed. IE 8 seems to have gotten rid of this problem completely. All other browsers seem to act as expected, so this is almost purely a Firefox issue.


Cause

Although I can only speculate about the true cause of this problem without diving into the code, after a few test I believe I narrowed down the root cause of this problem. The problem seems to be caused by the way that Firefox decided to handle event dispatching in JavaScript. Like in dispatchers in other browsers, the events are entered into a priority queue by time. However, unlike in Opera for example where an event handler is executed to its' very end, here it can be interrupted mid-way through when waiting for I/O. Modern operating systems usually block threads/processes waiting for I/O and let other processes run in the meantime. When I/O is ready to be consumed, the thread/process wakes up. This seems to fit our symptoms perfectly. Opening a new window usually results in a new thread or a new process, and since a request to open a URL in a new window is an I/O request, the thread is blocked. Now a new event can be serviced and it's handler can be invoked, but in our case it is the same event and the same handler. This is exactly what causes the problem, the order events in the queue are serviced generates some unexpected results.

To convince ourselves lets create a little experiment:

<html>
<head>
	<title>Popup Window Experiment</title>
	<script language="javascript">
	<!--
	// Call count will count the number of interrupted calls, if there are no interruptions
	// then it will always be 1 when the function body is executed otherwise it will be more than 1
	// meaning that the previous call to the handler did not get to complete it job and decrement the
	// variable back to its original value before the call.
	var callsCount = 0;
	var callsCount2 = 0;
	var myWinObj = null;


	// this handler will be blocked when waiting for I/O, we will see this by observing
	// pop-up windows with numbers 1, 2, 3, 4... matching the number of clicks while waiting for I/O
	function blockedHandler() {
		callsCount++;
		myWinObj = window.open("", "myWin", "width=100,height=100");
		myWinObj.document.write(callsCount);
		callsCount--;
	}


	function pause(millis) {
	        var date = new Date();
	        var curDate = null;


	        do {
	        	curDate = new Date();
	        } while(curDate-date < millis)
	}


	// this handler will not be blocked, demonstrating that when there is no I/O event handlers
	// are not blocked (also known as non-preemptive behaviour)
	function nonBlockedHandler() {
		callsCount2++;
		pause(3000);
		document.body.innerHTML += '<br />' + callsCount2;
		callsCount2--;
	}
	-->
	</script>
</head>
<body>
	<p>
		Click on the following link multiple times really fast, to see that you will get different numbers in each new popup window in
		Firefox:<br /><a href="javascript:blockedHandler();">Blocked on I/O Popup Demo</a>
	</p>
	<p>
		Note: how the lowest number (1) is on top, indicating the first call finishes last.
	</p>
	<p>
		Click here to see that non-I/O events do not get interrupted during the execuation of their event handler:<br />
		<a href="javascript:nonBlockedHandler();">Not-Blocked on non-I/O</a>
	</p>
</body>
</html>


This experimental code is very important to understand since we will use it as the basis for our work around. You can try it on this page here.

The blockedHandler  function demonstrates how an I/O event -- loading the url into the new window -- is interrupted in the middle, evident by the different numbers printed in the new windows. If the event handler was not interrupted then the count would drop back to the original value when it reaches the callsCount-- line. Instead, it enters another event handler and increments the value again. So the printed value will be different than the original. It is also important to note how the first call completes its work last (again we can tell by looking at the numbers).

The nonBlockedHandler function demonstrates how a non-I/O event, or a CPU intensive event, does not get blocked and runs until completion. In contrast to the previous test that gave different numbers in each window, this test produces the same number in the main application window. Since there is no I/O, nothing gets blocked, only a very intensive loop. This shows JavaScript is single threaded, the event handlers themselves do not get preempted. However, as the browser creates new threads for a new window in the blockedHandler these threads do get preempted by the OS when they ask for I/O.

To conclude our findings, JavaScript is indeed single thread, but the browser is using multi-threading which causes our symptoms. So how do we cure concurrency problems just by using our single threaded JavaScript?

Workaround

Initial Design

Not giving up on Firefox, I came up with a simple work around that looks like this:

var linkOnClickCalled = 0;


var linkOnClick = function()  {
	linkOnClickCalled++;
	if(linkOnClickCalled <= 1)  {
		window.open('newWin', 'width=400,height=400');
	}
	linkClickCalled--;
}

The idea was simple, use the results of our previous experiment to skip all the calls until our event handler returns. But this looks a little ugly and is not very scalable, therefore I cam up with the next design.

Refined Design

Notice the similarity of the initial design to how Semaphores work. A good place to look for inspiration is then of course the Linux c library semaphore.h, which we will use to improve this work around:



var windowObjectReference = null; // global variable


/**
 * The skip semaphore is the structure that allows us to ensure that a function is only
 * called initCount number of times before it returns. This only matters if the function
 * handles I/O in Firefox. For all other functions they will only be invoked once during
 * the function execution (not including recursive calls).
 * 
 * @param int initCount The number of calls allowed during function execution
 * @param Function func A function to be invoked
 * @return Function The inner function that will do the work of the skipping semaphore.
 */
var skipSemaphore = function(initCount, func) {
	var count = initCount;
	var sem_wrapper = function() {
		count--;
		if(count >= 0) {
			func();
		}
		count++;
	};


	return sem_wrapper;
};


// example of using the skip semaphore to write a function that opens a new window
var openPopup = skipSemaphore(1, function() {
	if(windowObjectReference == null || windowObjectReference.closed)
	{
		windowObjectReference = window.open("http://www.google.com", "myWin", "width=100,height=100");
	}
	else
	{
		windowObjectReference.focus();
	}
});


The first change I had to make was to invert the count to start at n and go to 0, just like a true semaphore. Next, I used the idea of closures to get rid of the global variable we used for counting. This piece was inspired by Douglas Crockford's, JavaScriptThe Good Parts, page 44 which describes a very similar technique to make caching reusable. It works due to JavaScript inner function staying alive even as outer functions return, allowing access to the outer function's variables.

Note that unlike true semaphores that block a thread if the count is smaller or equal to zero (<= 0), our code just skips that section. This is why I called it a skip semaphore here. Also, there is no queue to keep waiting events (since we skipped them). Finally, there is no signal and wait. So in the true spirit of JavaScript we have something not seen anywhere else, but still bares some resembles to other things.

References:

Mozilla Bug Report - https://bugzilla.mozilla.org/show_bug.cgi?id=279670

Opera Developer's Channel, Timing and Synchronization in JavaScript - http://dev.opera.com/articles/view/timing-and-synchronization-in-javascript/

Concurrency in JavaScript - http://endertech.blogspot.com/2010/01/investigating-javascript-concurrency.html

Mutex in JavaScript - http://www.developer.com/lang/jscript/article.php/3592016/AJAX-from-Scratch-Implementing-Mutual-Exclusion-in-JavaScript.htm

Separate Processes For Tabs, Mozilla Wiki - https://wiki.mozilla.org/Content_Processes

Douglas Crockford, JavaScript: The Good Parts, 2008 Yahoo Press




Add this page to your favorite Social Bookmarking websites
 
Last Updated on Sunday, 12 June 2011 10:51
 
More articles :

» Getting Reliable z-index Cross-Browser

Turns out it is not as easy as one might think to get thecorrect z-index of an element using a javascript call like $(element).css(‘z-index’). The problem is how browser vendors apply the z-indexto an element. But, no worries I have created a...

» 2D Sprites in OpenGL

OpenGL is a very powerful graphics library that is more commonly used for 3D graphics, but what about 2D graphics? Well there is little to no documentation on how to use it for a pure 2D application. This is exactly where this article comes-in.

» Script Tag Stripping Workaround in Joomla

If you ever tried inserting javascript into a Joomla article you may find it is a very difficult task; so, I went a head and made a plug-in to make this fast and easy. The reason why joomla makes it so difficult is because cross site scripting...

» Snow in JavaScript

Winter is finally over, but we can still make nice digital snow to cool us down during hot summer days. We will start by considering the path snow flakes take before they hit the ground, then we will find out how to implement it...

» Escaping HTML in Java

HTML uses some special characters to control how a page is displayed. These characters need to be escaped before placed on a page if they are to be displayed as part of the page content (and not just to control how the page appears). This is similar...

Add comment


Security code
Refresh