A very ugly (but working) alternative to ScriptErrorsSuppressed in WPF Browser Control

I recently worked on an application that needed to automate a website in the background. It fills out some forms, invokes some buttons and checks if all the navigation works fine.

Needless to say, the user of the application should not even notice that there is a website in the background.

I needed to use the webbrowser control as the website is a really fantastic example of how to NOT implement a web site. Everything the user does causes JavaScripts to execute and it's a nightmare (if not impossible) to solve this by sending custom POST requests to the webserver.

So I went with the webbrowser control which works really fine when used to fill out forms and invoke buttons in context of the website. It's still a lot of spaghetti code that comes together but it actually works.

For V2 of the application, i switchted from WinForms to WPF. The first thing I've learned was that it is really no good idea to use the WPF webbrowser control as it lacks some features of the WinForms control. One of them was the ScriptErrorsSuppressed-Property that prevents webpages from displaying MessageBoxes (not only caused by ScriptErros).

The solution is of course to use a WinFormsHost inside your WPF window and place a WinForms webbrowser control inside of it. But while you now have access to a much more complete API and can SET the ScriptErrorsSuppressed property, this property has actually no effect.

No matter what you are told in some dev forums (for example: it only works when the application runs in full trust) - it never works. Never ever. Don't waste your time. I spent hours trying dozens of different "solutions" to get this working, still each and every MessageBox popped up and remindet the user that there is some browser automation happening in the background.

As there is no clean solution on this and everything looks like Microsoft is not interested in changing this situation, I decided to go the ugly way with a dirty hack - I use COM Interop to the Win32 API call GetLastActivePopup that returns the window handle of the last popup a given window has created. This also works for MessageBoxes that are created by webbrowser control.

The trick now is to launch a worker thread every time you do some browsing and let this thread check frequently if your browser window raises any popups. If so, use another COM call to destroy this popup immediately.

I have very good results using this technique. That means: it works reliable and the end-user doesn't even notice that there's something going one most of the time as the check happens every 10 ms in my configuration. This short interval does not cause any performance issues or more than regular CPU load during execution.

Everything you need is here

The good news is: it is possible to use this out-of-the-box and you can use my code 1:1 in your project.

Firstly, here's the class that contains all the logic. Include it just like below in your project:

private class PopupHandler
{
    #region Interop

    [DllImport("user32.dll")]
    static extern IntPtr GetLastActivePopup(IntPtr hWnd);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);

    static uint WM_CLOSE = 0x10;

    #endregion

    public IntPtr OriginalWindowHandle { get; private set; }

    private bool isCanceled = false;

    public PopupHandler(IntPtr originalWindowHandle)
    {
        this.OriginalWindowHandle = originalWindowHandle;
    }

    public void Cancel()
    {
        isCanceled = true;
    }

    public void Execute(object windowHandle)
    {
        while (!isCanceled)
        {
            // Find a possible open IE popups/alert and close it
            IntPtr lastPopupHandle = GetLastActivePopup(OriginalWindowHandle);
            
            if (lastPopupHandle.ToString() != "0" & OriginalWindowHandle != lastPopupHandle)
                SendMessage(lastPopupHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);   // Sends close Message to popup

            // Sleep a little to prevent full CPU load
            Thread.Sleep(10);
        }
    }
}

 

Next, here is an example how to call this class in a multithreaded way:

// Launch Popup Handler Thread
WindowInteropHelper wih = new WindowInteropHelper(this);

PopupHandler popupHandler = new PopupHandler(wih.Handle);
Thread popupHandlerThread = new Thread(popupHandler.Execute);
popupHandlerThread.Start();

// Now do your navigation
// this.browser.Navigate(@"http://www.google.at");

 

And that's it. This will do all the work.

Short explanation: this in the above context is the WPF window. To get the window handle of a WPF window, we need to use the WindowInteropHelper class. And with wih.Handle we pass the window handle to the PopupHandler class, and actually this information is all the PopupHandler class needs to do its job.

Just one important notice: a WPF window does not get a window handle until it was showed at least one time. So you *will* have to call myWindow.Show() at least once before using this class. It's ok if you call myWindow.Hide() immediately after that. Consider setting the default window state to minimized to prevent the user seeing some flickering on the screen.

What is left for you to do? Of course, you want to stop this thread after you have completed browsing. The PopupHandler class has a handy Cancel-Method for that. Just call it when you are finished.

I hope this helps some fellow developers to save many valuable hours of their lives :)

Of course if anyone out there can come up with a cleaner solution, please let me know!

3 comments for “A very ugly (but working) alternative to ScriptErrorsSuppressed in WPF Browser Control”

  1. Gravatar of Thomas WooThomas Woo
    Posted Sunday, June 20, 2010 at 12:16:10 AM

    Thanks so much for this solution, I was trying to find some way to stop those damn script errors and it works great!

  2. Gravatar of Bernhard KönigBernhard König
    Posted Monday, June 28, 2010 at 1:45:40 PM

    Hi Thomas,

    great to hear that this works well for others too!

    This problem is a nightmare, isn't it ;)

    cheers,
    b.

  3. Gravatar of dominiquedominique
    Posted Thursday, March 31, 2011 at 4:44:00 AM

    Thanks is working but I have to adapt the code to work outsite of the current windows in order to get the handle. "this" doesnot work
    thanks again

Post a comment