I was always under the impression that, in all browsers, JavaScript runs on the UI thread. As it turns out, this is not the case, which has some interesting side-effects.
If you're using Internet Explorer, Firefox or Safari, when you click on any of the buttons or links below they will become active, some time will pass while the loop runs, the click timestamp will appear, and then the alert will appear.
If you are using Opera (version 9 at least), the button or link will click, the timestamp will appear immediately and when the processing is complete the alert will appear. In fact, you can continue clicking on the button or link, but now, while one is busy processing, the others are queued (as one would expect in a single threaded environment). Not even removing the event listener prevents clicks from processing! Only the last method, which removes the events and then hands processing back to the browser (using setTimeout) works at preventing multiple clicks in Opera. This also has the interesting side-effect of showing the click timestamp immediately in all browsers.
Event.observe(window, "load", function() {
Event.observe($("processButton"), "click", process);
Event.observe($("processLink"), "click", process);
Event.observe($("processWithFlagButton"), "click", processWithFlag);
Event.observe($("processWithFlagLink"), "click", processWithFlag);
Event.observe($("processWithEventRemovedButton"), "click", processWithEventRemoved);
Event.observe($("processWithEventRemovedLink"), "click", processWithEventRemoved);
Event.observe($("processWithTimeoutButton"), "click", processWithTimeout);
Event.observe($("processWithTimeoutLink"), "click", processWithTimeout);
});
function process() {
new Insertion.Top("log", "Click timestamp: " + new Date().getTime() + "<br />");
for(var i = 0; i < 10000000; ++i);
alert("Done");
}
var flag = false;
function processWithFlag(event) {
if(flag) return;
flag = true;
new Insertion.Top("log", "Click timestamp: " + new Date().getTime() + "<br />");
for(var i = 0; i < 10000000; ++i);
alert("Done");
flag = false;
}
function processWithEventRemoved(event) {
Event.stopObserving(Event.element(event), "click", processWithEventRemoved);
new Insertion.Top("log", "Click timestamp: " + new Date().getTime() + "<br />");
for(var i = 0; i < 10000000; ++i);
alert("Done");
Event.observe(Event.element(event), "click", processWithEventRemoved);
}
function processWithTimeout(event) {
Event.stopObserving(Event.element(event), "click", processWithTimeout);
new Insertion.Top("log", "Click timestamp: " + new Date().getTime() + "<br />");
setTimeout(function() { doProcessWithTimeout(Event.element(event)); }, 1);
}
function doProcessWithTimeout(element) {
for(var i = 0; i < 10000000; ++i);
alert("Done");
Event.observe(element, "click", processWithTimeout);
}