« Home | Ajax Safari Update » | An AJAX Safari... » 

Wednesday, October 19, 2005 

Onbeforeunload throwing errors in IE - coding around it in ASP.NET

Some of the most popular questions on any web dev forum are:

  1. How do I trap the back button / stop people from clicking back?
  2. How do I stop someone from viewing source?
  3. How do I stop someone from closing a window/browser?
Short answer for all is: you can't! Especially if you are supporting multiple browsers & multiple versions. There are tricks for the first two questions - and I notice Gmail is pretty clever to redraw the UI without a server trip when you click back. I'm pretty confident in saying - you can't stop someone determined from viewing the source. Problem being that your webclient will need the source to display it!

How do you stop someone from closing a browser? Can you imagine how nasty pop-up ads would be if you couldn't close them? There is an event in IE onbeforeunload. This will pop-up a dialog asking 'Are you sure you want to navigate away from this page?' plus your own text - if you attempt to navigate away from a page.

I usually try to talk people out of this - it is kind of bad usability. I must be losing my powers or getting complacent because I wound up using this on a project recently. This is how I discovered a problem where a user clicking 'Cancel' on the dialog will throw an error in IE. It only seems to happen if the navigation is initiated by client-side script. View a demo of it here.

A google for 'onbeforeunload "unspecified error"' reveals a few articles about it. All centre around catching the 'unspecified error' in your javascript. Unfortunately my error was being thrown from within an AutoPostBack from a DropDownList - so I don't have access to the script to catch the error. There's a demo here, view the source here.

More scavenging around on Google and I find a way to hijack the postback. This is done by:

  • stashing ASP.NET's postback function into a variable
  • replacing the postback function with your own
  • your function does what ever you have to do
  • then calls the 'stashed' postback function

var __oldDoPostBack = __doPostBack;
__doPostBack = CatchExplorerError;

function CatchExplorerError (eventTarget, eventArgument)
{
    try
    {
        return __oldDoPostBack (eventTarget, eventArgument);
    } catch (ex)
    {
        // don't want to mask a genuine error
        // lets just restrict this to our 'Unspecified' one
        if (ex.message.indexOf('Unspecified') == -1)
        {
            throw ex;
        }
        else
        {
            alert('caught the error!');
        }
    }
}

This will work nicely. Its important to note that firefox will enter into the catch - because it doesn't like the onbeforeunload event. Also worth noting that the exception object also has a 'description' property in IE. I was originally testing this for the 'unspecifed' string - which throws another error in firefox. Both browsers seem happy if I use the 'message' property. The reference for the IE JScript Error object is here. The firefox reference doesn't appear to be complete?

This still leaves one more problem: the select box is still has the new item selected. There's not really any way to know that the user has clicked cancel on the 'Are you sure?' dialog. I could put something inside the catch in the CatchExplorerError. This would be a very very dodgy solution - as I'm relying on an error to occur to produce the UI that I want. What will happen when/if this bug gets fixed! A less dodgy solution (although I'm not entirely happy with it) would be to return the select box to its original selection in the onbeforeunload. This takes place before the dialog even appears. Yet at this point the browser has 'decided on' the form it will be sending to the server. So setting the selection here isn't submitted up to the server.

The script 'remembers' the original selection of the select box by storing it in an expando property.

<select id="DropDownList1" onclick="this.oldIndex = this.selectedIndex">

Then I set the select box back to its stored value when onbeforeunload fires.

function body_onbeforeunload()
{

    var DropDownList1 = document.getElementById('DropDownList1');
    if (DropDownList1.oldIndex != undefined)
    {
        // return the selectbox to its oldIndex
        DropDownList1.selectedIndex = DropDownList1.oldIndex;
    }

    event.returnValue = "You sure?";
}

View a demo of the completed solution here, and the source here.

Labels: ,

It's also annoying when sites try to prevent users from "Right Click-->Save Picture As..." by popping up an alert saying "right click is disabled". My favorite client-side script abuse is when developers do form validation, but don't back it up with server side form validation.

My work around for that was usually to open the page in Netscape which didn't support that event.. If you want to get serious you can always view it thru a snooping proxy like fiddler:
http://www.fiddlertool.com/fiddler/

Since I do not want to add a try&catch to existing scripts, I do the following: just before returning a message in the onbeforeunload function, I 'turn off' error prompting temporarily.


1: I store a copy of window.onerror (null or a function) under window.onerror2,

2: window.onerror is set to allways returns true, so no error alerts will be thrown,

3: the original value of window.onerror will be restored after a delay, this will be after the user clicked cancel

Works fine on IE7, FF3 and Chrome1

code:
window.onerror2=window.onerror;
window.onerror=function() {return true}
setTimeout(function() {window.onerror=window.onerror2} , 10);

Checking for the exception number should work for non-English and English browsers.

if (ex.number != -2147467259)

Nice pick up Doug. Relying on an English string is exceptionally hacky on my part!

thanks man!, really saved my time..

Post a Comment