Working around IE7s prompt bug, er feature

Filed: Sun, Jan 14 2007 under Programming|| Tags: workaround ie7 javascript prompt

One of Internet Explorer's many gotcha's is the fact that Microsoft decided to create a security wall around the javascript "prompt" command. When a script tries to call a prompt, Internet Explorer will drop a security warning line at the top of the window -- hilarity ensues from there.

In Internet Explorer 7.0 when you try to use the javascript "prompt" command you'll get a security warning at the top of the window stating:

This website is using a scripted window to ask you for information. If you trust this website, click here to allow scripted windows...

The funny thing is that even if the user notices the warning and then is savvy (or foolish) enough to allow the script to run, no prompt box may actually appear because even while the security line is asking for permission to run the prompt, IE has already disallowed the prompt and the script has moved on. Just to really make things interesting, even after giving the page scripting permissions, if the user hits reload he'll have to give permission all over again (and miss that first prompt).

What this effectively means is that, when Microsoft released Internet Explorer 7.0 and then pushed it as a mandatory upgrade via Windows Update, they broke hundreds of thousands of pages which used the javascript prompt command. Pages nearly as old as the world wide web itself.

Microsoft considers this a "feature", not a bug. According to the IE Blog:

As you discovered, the secunia article discusses spoofing the user based on a lack of dialog origin. Variations on this theme were the motivation behind this security mitigation. However, we had a few other reasons, which I won't discuss in depth, but the bulk of them are usability issues that we didn't have resources to fix, so we opted to block the dialog itself by default: site branding/customization, text limitations (as you mentioned), insecure text entry (commonly spoofed as a password field). We appreciate the feedback posted here on simple things we can update in future versions to improve the experience.

So while every other browser on the planet can handle javascript prompts -- and have done so, pretty much since javascript was first stuffed inside the browser -- Microsoft didn't have the resources to deal with it and so, effectively, disabled it.

The problem with this is that prompt is "modal", meaning everything basically comes to a halt until the user addresses the dialog box; either by canceling it, or by entering some data. This is unique in Javascript. Other than prompt, only a syncronous AJAX call has the ability to stall a script until requested input has been resolved.

For instance...

alert(prompt('say something'))

This stops javascript from continuing until the prompt box is addressed, then and only then will the alert box appear. The modality of the prompt box prevents javascript from moving on until the user has performed some action on the box.

Go ahead and see for yourself. Non IE7 users will see the prompt box, IE will get a security warning which, even when accepted won't display the prompt. You should note that the alert popped up immediately. IE totally ignored the prompt command while it was busy asking you if it was ok to show the prompt. So even when you accept the security warning, it's too late, the script that wanted the prompt has long since moved on.

So with IE7 the prompt command is forever broken, and with Microsoft commanding, for now at least, the lion's share of the browser market, few developers will consider using prompt as a means of data acquisition. This doesn't offer much help for the 1,470,000 (according to Google) web pages already written that are now broken.

While we can't restore the perfect modal prompt box, we can simulate it somewhat. The following script will call the standard prompt for regular browsers but for IE7 it will construct its own simulated dialog box, grey out the screen so the user can't fiddle with any other elements on the web page until the box is addressed, and then pass the data back to your function. The catch is it passes data back to one of your functions, your code will not stop running while the user is filling in the simulated prompt box. The script will start the dialog, then return control back to the browser and your scripts. When the user is done, the dialog box will call the function promptCallback(val) which you must write to handle the incoming data.

You can see an example of the workaround script here. This example has been modified to call the workaround dialog box regardless of which browser you are using. A live, working example of this script can be found by checking out the Introduction to Regular Expressions in Javascript article which uses the prompt box extensively in the second half of the tutorial.

You can download the script here.

To use it set up an include line as such...

<script type='text/javascript' src='IEprompt.js'>

In place of prompt you use, IEprompt.

IEprompt('some descriptive text', 'default value');

To accommodate the lack of true modality in IE7's dialog, the value will be passed to a function titled promptCallback(val). You will need to create this somewhere in your program. Here's a small example.

function promptCallback(val) {
   alert(val);
}

val is the the value the user entered. If it is null or empty the dialog was either canceled or the user didn't enter any text and clicked ok anyway. If the user entered text it will be passed in val. Just to clarify, regardless of the user's browser the result of the prompt will be pased to your promptCallback(val) function.

Here's a complete sample.

<script type='text/javascript' src='IEprompt.js'></script>
<script type='text/javascript'>
function promptCallback(val) {
   alert(val);
}

IEprompt('Enter some text','default value');
</script>

Not quite alert(prompt('enter some text','default value')), but at least we've recovered some functionality. To maintain a consistent interface across differing browsers you can remove the IE7 check and have all the browsers use the new, generated dialog box.

Here is the source code for the include file. It's well commented so you should be able to follow along.

///////////////////////////////////////////////////////////
// Usage IEprompt("dialog descriptive text", "default starting value");
// 
// IEprompt will call promptCallback(val)
// Where val is the user's input or null if the dialog was canceled.
///////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////
// This source code has been released into the public domain
// January 14th, 2007.
// You may use it and modify it freely without compensation
// and without the need to tell everyone where you got it.
///////////////////////////////////////////////////////////


///////////////////////////////////////////////////////////
// You must create a promptCallback(val) function to handle
// the user input.  If you don't this script will fail and
// Bunnies will die.
///////////////////////////////////////////////////////////


///////////////////////////////////////////////////////////
// These are global scope variables, they should remain global.
///////////////////////////////////////////////////////////
var _dialogPromptID=null;
var _blackoutPromptID=null;
///////////////////////////////////////////////////////////


function IEprompt(innertxt,def) {

   that=this;   // A workaround to javascript's oop idiosyncracies.

   // Check to see if this is MSIE 7.   This isn't a great general purpose
   // detection system but it works well enough just to find MSIE 7.
   var _isIE7=(navigator.userAgent.indexOf('MSIE 7')>0);

   this.wrapupPrompt = function (cancled) {
      // wrapupPrompt is called when the user enters or cancels the box.
      // It's called only by the IE7 dialog box, not the non IE prompt box
      if (_isIE7) {
         // Make sure we're in IE7 mode and get the text box value
         val=document.getElementById('iepromptfield').value;
         // clear out the dialog box
         _dialogPromptID.style.display='none';
         // clear out the screen
         _blackoutPromptID.style.display='none';
         // clear out the text field
         document.getElementById('iepromptfield').value = '';
         // if the cancel button was pushed, force value to null.
         if (cancled) { val = '' }
         // call the user's function
         promptCallback(val);
      }
      return false;
   }

   //if def wasn't actually passed, initialize it to null
   if (def==undefined) { def=''; }

   if (_isIE7) {
      // If this is MSIE 7.0 then...
      if (_dialogPromptID==null) {
         // Check to see if we've created the dialog divisions.
         // This block sets up the divisons
         // Get the body tag in the dom
         var tbody = document.getElementsByTagName("body")[0];
         // create a new division
         tnode = document.createElement('div');
         // name it
         tnode.id='IEPromptBox';
         // attach the new division to the body tag
         tbody.appendChild(tnode);
         // and save the element reference in a global variable
         _dialogPromptID=document.getElementById('IEPromptBox');
         // Create a new division (blackout)
         tnode = document.createElement('div');
         // name it.
         tnode.id='promptBlackout';
         // attach it to body.
         tbody.appendChild(tnode);
         // And get the element reference
         _blackoutPromptID=document.getElementById('promptBlackout');
         // assign the styles to the blackout division.
         _blackoutPromptID.style.opacity='.9';
         _blackoutPromptID.style.position='absolute';
         _blackoutPromptID.style.top='0px';
         _blackoutPromptID.style.left='0px';
         _blackoutPromptID.style.backgroundColor='#555555';
         _blackoutPromptID.style.filter='alpha(opacity=90)';
         _blackoutPromptID.style.height=(document.body.offsetHeight<screen.height) ? screen.height+'px' : document.body.offsetHeight+20+'px'; 
         _blackoutPromptID.style.display='block';
         _blackoutPromptID.style.zIndex='50';
         // assign the styles to the dialog box
         _dialogPromptID.style.border='2px solid blue';
         _dialogPromptID.style.backgroundColor='#DDDDDD';
         _dialogPromptID.style.position='absolute';
         _dialogPromptID.style.width='330px';
         _dialogPromptID.style.zIndex='100';
      }
      // This is the HTML which makes up the dialog box, it will be inserted into
      // innerHTML later. We insert into a temporary variable because
      // it's very, very slow doing multiple innerHTML injections, it's much
      // more efficient to use a variable and then do one LARGE injection.
      var tmp = '<div style="width: 100%; background-color: blue; color: white; ";
      tmp += 'font-family: verdana; font-size: 10pt; font-weight: bold; height: 20px">Input Required</div>';
      tmp += '<div style="padding: 10px">'+innertxt + '<BR><BR>';
      tmp += '<form action="" onsubmit="return that.wrapupPrompt()">';
      tmp += '<input id="iepromptfield" name="iepromptdata" type=text size=46 value="'+def+'">';
      tmp += '<br><br><center>';
      tmp += '<input type="submit" value="&nbsp;&nbsp;&nbsp;OK&nbsp;&nbsp;&nbsp;">';
      tmp += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
      tmp += '<input type="button" onclick="that.wrapupPrompt(true)" value="&nbsp;Cancel&nbsp;">';
      tmp += '</form></div>';
      // Stretch the blackout division to fill the entire document
      // and make it visible.  Because it has a high z-index it should
      // make all other elements on the page unclickable.
      _blackoutPromptID.style.height=(document.body.offsetHeight<screen.height) ? screen.height+'px' : document.body.offsetHeight+20+'px'; 
      _blackoutPromptID.style.width='100%';
      _blackoutPromptID.style.display='block';
      // Insert the tmp HTML string into the dialog box.
      // Then position the dialog box on the screen and make it visible.
      _dialogPromptID.innerHTML=tmp;
      _dialogPromptID.style.top=parseInt(document.documentElement.scrollTop+(screen.height/3))+'px';
      _dialogPromptID.style.left=parseInt((document.body.offsetWidth-315)/2)+'px';
      _dialogPromptID.style.display='block';
      // Give the dialog box's input field the focus.
      document.getElementById('iepromptfield').focus();
   } else {
      // we are not using IE7 so do things "normally"
      promptCallback(prompt(innertxt,def));
   }
}