Migrate intranet applications from IE6
4/10/2010Recently I was called to help figure out how we'd migrate the company to IE8. Like many, we have some older vendor applications that support only IE6. We're also forbidden to change them, a policy that has locked us into the not-so- finely-aged IE6.
But it turns out that IE8 still has a "quirks mode" for rendering compatibility with IE5. Check out this MSDN article from 1998 which talks about their dedication to compatibility with future browsers:
** Compatibility. ** [...] backward compatibility with previous versions of Internet Explorer (and other browsers) is a top priority of the Internet Explorer team, which means that customers who use your WebBrowser-based application can upgrade their Internet Explorer components without "breaking" your application. For example, if your application hosted the Internet Explorer 4.0 WebBrowser control, and your customer upgraded their browser to Internet Explorer 5, your application will still work.
This is still true, believe it or not. So I created a simple C# application, dragged the web browser control to a form and ran the resulting exe. All I needed was to call Navigate() and set a size for the window and... done. That was it. Almost the entire website worked just like that, running IE8, from ActiveX components to layout and even the massive gobs of Javascript.
For many IE6 webapps, that might be enough. Most of our users are already accustomed to clicking a shortcut on their desk, and few would notice if one day that shortcut launches an application instead of ieexplorer.exe.
The real beauty is you can deploy this right away. It'll work with whichever IE the target machine has without change, so there doesn't have to be a great push of IE8 and whatever new browser control applications. That'd be too much change all at once and would make the migration more difficult.
If making an exe for each webapp sounds like too much work, consider that the other option they'd been looking into was some sort of virtualization product from Microsoft. While migrating IE can be difficult because of all the proprietary code out there, I fought against giving more money to Microsoft to fix a problem that they helped cause. And I was even more against buying Windows servers to fix a Windows client problem. It not only struck me as wrong but as a technically complicated deployment path that surely wouldn't make support any easier.
Advanced Hacks
Simply hosting IE took care of everything save one: popup windows. The webapp used a lot of new windows and (oddly) some weird hack using showModalWindow like a timeout. I assume that was because it's the only Javascript call that I can think of that'll block.
Firstly, you'll want to use csexwb2 . It's an open source project that'll help take care of a lot of the more nuanced problems when dealing with IE. It also doesn't rely on any generated Interop assemblies so it's pretty easy to deploy as well.
If found it was easiest to work in Javascript in the hosted IE than try to implement a bunch of event handlers for obscure IE objects. Most of it is poorly documented and I'm expected to know the difference between three or four interface versions with the same names. It's frustrating.
On the form's onLoad() method, I setup a ScriptObject instance which is used for communication from Javascript to the host.
` [sourcecode language='csharp']protected virtual void onLoad(object sender, EventArgs e) {
// this object becomes 'window.external' in javascript
this.browser.ObjectForScripting = new ScriptObject(this);
this.browser.Navigate(url);
}[/sourcecode] `
Using the csExWB.NavigateComplete2EventHandler you can access the IHTMLWindow2 object to run your own Javascript. I do this to replace a couple functions with my own code in ScriptObject. The key is to run it for all the document frames or some code may run without your modifications:
` [sourcecode language='csharp']
private void navigationComplete(object sender, csExWB.NavigateComplete2EventArgs e) {
try {
List frames = this.browser.GetFrames();
foreach (IWebBrowser2 wb in frames) {
try {
setWindowProperties((IHTMLWindow2) ((IHTMLDocument2)wb.Document).parentWindow);
}
catch (Exception exc) {
log.Fatal("setWindowProperties failed for frame", exc);
}
} `
setWindowProperties((IHTMLWindow2) this.browser.GetActiveDocument().parentWindow);
}
catch (Exception ex) {
log.FatalException(setWindowProperties error on document complete", ex);
}
}
[/sourcecode]
setWindowProperties looks like the below. I declare anonymous functions so I can add logging (that's not included here):
` [sourcecode language='csharp']
private void setWindowProperties(IHTMLWindow2 win) {
win.execScript(@"
window.dialogArguments = window.external.dialogArguments; `
window.showModalDialog = function(url, args, options) {
return window.external.showModalDialog(url + '', args, options + '');
}
window.open = function(url, name, features, replace) {
return window.external.open(url, name, features, replace);
};
", "javascript");
}
[/sourcecode]
The ScriptObject class is pretty straight-forward. It needs to declare functions for handling open() and showModalDialog(). Nothing special there, just be sure to use ShowDialog() for modal and Show() for open like normal.
The one problem I had was returning the newly created window instance in open(). I stumbled a bit there because I'm unfamiliar with C# (or Windows in general :-), but it ended up being pretty easy, too:
` [sourcecode language='csharp']
// wait for load
while(dialog.browser.GetActiveWindow2() == null) {
Application.DoEvents();
log.Trace("waiting for window...");
Thread.Sleep(100);
} `
return dialog.browser.GetActiveWindow2();
[/sourcecode]
That's about it. I got a old laptop with a base image + IE8 and went to testing. Everything has been working fine so far.
Please let me know if you have questions below. Improvements are welcome, as always.