1: Introduction

.NET 2.0 has a new WebBrowser control in the System.Windows.Forms namespace. This control itself is very useful, but doesn't supply some events that might be needed in certain situations. This article explains how to extend the WebBrowser control and add functionality for things like pop-up blocking, script error handling, and handling new windows in a tabbed browser environment.

For extending the WebBrowser control, some features are not documented in the Help files of the .NET Framework. Not letting us be stopped by the "This method supports the .NET Framework infrastructure and is not intended to be used directly from your code." message, it is possible to create an object that implements IWebBrowser2 and use all the functionality of the underlying browser. Besides this, DWebBrowserEvents2 can be implemented for adding events to the control.

This article assumes that you have already some knowledge of the browser interfaces IWebBrowser2 and DWebBrowserEvents2. Some knowledge about COM Interop and interfaces is also required.

2: The goals, challenges, and solutions

The goals of this component are:

Handling script errors in a neat way

Blocking unwanted pop-ups

Enabling functionality for tabbed browsing or MDI browsing

Making sure that a window is closed when it is closed by script

This section explains the problems associated with the goals and their solutions, in a short form. The next section goes more into the coding details.

Handling Script Errors

The WebBrowser control has a ScriptErrorsSuppressed property... Setting this property to true does actually a bit more than it is supposed to. It not only disables the script error dialog, but also the dialog for logging on to a secure website with user certificates... What if we still want that functionality, or we would like to be notified when a script error has taken place, or we would like to know all the details about the script error?

The script error can be caught by the HtmlWindow.Error event. This event fires whenever a script error occurs, with all the details. The challenge is that HtmlWindow is to be accesed with the HtmlDocument object, which is not always available. HtmlDocument comes available as soon as the Navigated event is fired. But what if the user refreshes the browser with F5? Sorry, the Navigated event doesn't fire. After some testing, I found that the only reliable way was to use the DownloadComplete event, which is not part of the default WebBrowser control.

Solution:

Implement DWebBrowserEvents2

Create a DownloadComplete event

When DownloadComplete fires, subscribe to the HtmlWindow.Error event

Use the error event for obtaining the script error information

Set the Handled property to true to suppress the script error

Blocking unwanted pop-ups

Pop-ups are most of the time not very welcome, or could be inappropriate. To block these things, some additional information is needed. NewWindow3 gives this information when the user uses Windows XP SP2, or Windows 2003 SP1 or better. If this event is not fired, NewWindow2 takes its place. When NewWindow3 is fired, you can check:

If the user initiated the action that leads to the new window

If the user holds the override key (the Ctrl Key)

If it is a pop-up displayed because of a window that is closing

The URL that is going to be opened

And more...

Using NewWindow3 clearly is very interesting for this purpose. To use this event, DWebBrowserEvents2 needs to be implemented.

Solution:

Implement DWebBrowserEvents2

Create a new event and a new event arguments class

Launch this event with the appropriate information

After the event is fired, see if the navigation needs to be canceled

Enabling functionality for tabbed browsing or MDI browsing

Tabbed browsing seems to gain popularity these days. For Internet Explorer 7, this is one of the new features. The challenge in tabbed browsing is that you need to create a window when this is needed by scripts or links. Besides this, window name resolution should work over multiple windows or tabs. (For example: <A href="http://SomeSite" target="SomeWindowName"/>) To achieve this, the automation object (called ppDisp in the NewWindowX event, and Application in the IWebBrowser2 interface) should be passed from the new browser back to the event. To get access to the Application property, it is needed to get a reference to the underlying IWebBrowser2 interface.

Solution:

Override AttachInterfaces and DetachInterfaces

Store a reference to the IWebBrowser2 interface object

Create a property called Application that exposes the Application property of the interface

Implement the DWebBrowserEvents2 interface

Listen for NewWindow2 and/or NewWindow3 events

Whenever an event is raised, create a new instance of the browser control

Assign the event parameter ppDisp to the Application property of the new instance

Making sure that a window is closed when it is closed by script

When you invoke window.close() in JScript, it looks like the WebBrowser control hangs. Somehow, it can't be used for navigation, nor can it be used for anything else. It would be nice if we know when this happens. There are several events that fire when this happens, but none of the events gives us actually the information needed. Overriding WndProc and seeing if the parent is notified that the browser is destroyed, is the only reliable solution. (If someone knows how to get WindowClosing to work, it would be nice here!)

Solution:

Override WndProc

Check for the message WM_PARENTNOTIFY

Check for the parameter WM_DESTROY

If this is the case, fire a new event (this event is called Quit, in the example)

3: Creating the extended WebBrowser component

From the last section, we have seen that all of this basically comes down to two things:

Implement an object of type IWebBrowser2, for obtaining the Application property of the browser

Implement DWebBrowserEvents2 for firing events

Implementing IWebBrowser2

The WebBrowser control has two methods that are undocumented: AttachInterfaces() and DetachInterfaces(). These methods need to be used when you want to obtain a reference to the IWebBrowser2 interface.

///<spanclass="code-SummaryComment"><summary></span>/// An extended version of the <spanclass="code-SummaryComment"><seecref="WebBrowser"/> control.</span>///<spanclass="code-SummaryComment"></summary></span>publicclass ExtendedWebBrowser : System.Windows.Forms.WebBrowser
{
private UnsafeNativeMethods.IWebBrowser2 axIWebBrowser2;
///<spanclass="code-SummaryComment"><summary></span>/// This method supports the .NET Framework
/// infrastructure and is not intended
/// to be used directly from your code.
/// Called by the control when the underlying
/// ActiveX control is created.
///<spanclass="code-SummaryComment"></summary></span>///<spanclass="code-SummaryComment"><paramname="nativeActiveXObject"></param></span> [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protectedoverridevoid
AttachInterfaces(object nativeActiveXObject)
{
this.axIWebBrowser2 =
(UnsafeNativeMethods.IWebBrowser2)nativeActiveXObject;
base.AttachInterfaces(nativeActiveXObject);
}
///<spanclass="code-SummaryComment"><summary></span>/// This method supports the .NET Framework infrastructure
/// and is not intended to be used directly from your code.
/// Called by the control when the underlying
/// ActiveX control is discarded.
///<spanclass="code-SummaryComment"></summary></span> [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protectedoverridevoid DetachInterfaces()
{
this.axIWebBrowser2 = null;
base.DetachInterfaces();
}
...
}

This property can be used for creating a new window, and redirecting the browser to this new window, when a new window event is fired.

Implementing DWebBrowserEvents2

The following events are implemented in this sample:

NewWindow2 and NewWindow3 (for blocking pop-ups and creating new windows)

DownloadBegin and DownloadComplete (for handling script errors)

BeforeNavigate2 (if you want to see where you're going before even starting to get there)

To neatly implement DWebBrowserEvents2, it is best to create a privately nested class in the component. This way, all the events that are needed are on one place and easy to find. When we instantiate this class, we provide a reference to the caller, whose methods can be invoked for raising the events we need.

Events are not attached at component construction, but a bit later. There are two methods here that provide this and can be overridden. These are CreateSink() and DetachSink(). When adding this all up, we get something like this (note that some code has been cut for readability):

4: Using the component

In the last section, we created a new component. Now, it's time to use the new events and get the maximum functionality out of the browser. For each of the goals, the details are explained here.

Handling the script errors

In the sample application, there is a tool window that simply shows a list of errors that occured, with their details. A single-instance class holds the script errors' information and notifies the subscribers when this information has been changed. For handling these script errors, the BrowserControl first attaches to the DownloadComplete event, and next subscribes to the HtmlWindow.Error event. When this event is fired, we register the script error and set the Handled property to true.

Handling pop-ups should be user configurable. For the purpose of demonstration, I've implemented four levels, ranging from blocking nothing to blocking every new window. This code is part of the BrowserControl, and shows how to do this. After the new window is allowed, the example shows how to let the new browser participate in the window name resolution.

void _browser_StartNewWindow(object sender,
BrowserExtendedNavigatingEventArgs e)
{
// Here we do the pop-up blocker work
// Note that in Windows 2000 or lower this event will fire, but the
// event arguments will not contain any useful information
// for blocking pop-ups.
// There are 4 filter levels.
// None: Allow all pop-ups
// Low: Allow pop-ups from secure sites
// Medium: Block most pop-ups
// High: Block all pop-ups (Use Ctrl to override)
// We need the instance of the main form,
// because this holds the instance
// to the WindowManager.
MainForm mf = GetMainFormFromControl(sender as Control);
if (mf == null)
return;
// Allow a popup when there is no information
// available or when the Ctrl key is pressed
bool allowPopup = (e.NavigationContext == UrlContext.None)
|| ((e.NavigationContext &
UrlContext.OverrideKey) == UrlContext.OverrideKey);
if (!allowPopup)
{
// Give None, Low & Medium still a chance.
switch (SettingsHelper.Current.FilterLevel)
{
case PopupBlockerFilterLevel.None:
allowPopup = true;
break;
case PopupBlockerFilterLevel.Low:
// See if this is a secure site
if (this.WebBrowser.EncryptionLevel !=
WebBrowserEncryptionLevel.Insecure)
allowPopup = true;
else// Not a secure site, handle this like the medium filter
gotocase PopupBlockerFilterLevel.Medium;
break;
case PopupBlockerFilterLevel.Medium:
// This is the most dificult one.
// Only when the user first inited
// and the new window is user inited
if ((e.NavigationContext & UrlContext.UserFirstInited)
== UrlContext.UserFirstInited &&
(e.NavigationContext & UrlContext.UserInited)
== UrlContext.UserInited)
allowPopup = true;
break;
}
}
if (allowPopup)
{
// Check wheter it's a HTML dialog box.
// If so, allow the popup but do not open a new tab
if (!((e.NavigationContext &
UrlContext.HtmlDialog) == UrlContext.HtmlDialog))
{
ExtendedWebBrowser ewb = mf.WindowManager.New(false);
// The (in)famous application object
e.AutomationObject = ewb.Application;
}
}
else// Here you could notify the user that the pop-up was blocked
e.Cancel = true;
}

The reason the event is called StartNewWindow is that the code design guidelines do not allow an event to begin with "Before" or "After". "NewWindowing" doesn't have the same kind of ring to it :)

Using the Quit event

When the Quit event is fired, it's simply a matter of finding the right window or tab to close, and Dispose the instance.

5: Conclusion

The WebBrowser control is a good control for enabling web content in Windows applications. The additions in this article can be used to overcome the obstacles that developers face when they have no control over what web pages or other content the user might visit with their application. Hopefully, the next version of the .NET Framework will give us a little extra.

The sample application and source

The sample application is not a flashy UI, but it does demonstrate everything about this article. The code is commented, and hopefully gives enough information for helping you put your own solution together.

Acknowledgements

I would like to thank the following persons that made part of this article possible:

The technique for DWebBrowserEvents2 was in the bug list of Microsoft .NET 2.0. This has been used with modification.

The technique for WndProc was told by someone called "JoeBlow" on the MSDN forums, who had it derived from the MSKB article #253219.

This is my first article on The Code Project. Please excuse me for my English. Thanks for reading! If you can add anything or have suggestions or tips, please post a message below.

License

The code in this license may be used for any purpose, just let your users know where it came from, and share derived code under the same license as this one. Don't blame me if something goes wrong. More information can be found here.

If you wish to use and/or publish this in commercial closed-source applications, you have my consent. You may use this code under your own license when you do so.

History

27th of March 2006: First post of this article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Share

About the Author

I am - born in The Netherlands - living in Chile together with my wife. - a Microsoft Certified Professional Developer on all 3 areas (Windows, Web and Enterprise)- an MCITP on Microsoft SQL Server 2005 (Database Administrator)- an active programmer for about 14 years. - a business owner, of a Dutch company called "The Wheel Automatisering" (http://www.thewheel.nl)- a coder in C#, VB.Net and Managed C++. - someone who likes to share knowledge

For fun I like to go out with my dogs, enjoy the sun or write some articles that I share with the community.

I need to get the whole elements of a webpage, so I need to know as long as the documentCompleted. But when the webpage containes more than one ifrme inner pages or some javascript commands can fires DocumentComplete event, how to judge wether the documentCompleted event is arised when the webpage is completely finished.

I'm using WebBrowser control in my Windows .NET Form application and looking to block java script error only and not other popup like accessing secure website (https://), etc. Came across this article and thought I would be able to find solution to my problem. But I'm seeing that this demo is not handling the script error. I've a test website where I intentionally cause an error in java script code (in aspx file) by misspelling "var" to "va".

When I load that page, it shows the typical "Script Error" dialog box with message about continue running the script with "Yes|No" option. So I'm trying to understand what is missing here. What I need to do to WebBrowser control so that such error dialog doesn't show up.

Instead of using the default in your switch statement, you need to pass the message on to the base class regardless of what you are doing with it. I believe you window behavior will then be as desired. You may find you need to pass the message on BEFORE your switch depending on what you are doing. Either way, passing it on to the base class insures proper clean up.

I am new bie in DOT NET. can you please let me know how can i change default link in this application. By default its opening www.google.com i want to change it to www.yahoo.com from where can i do this.

I also want to disable right click context menu of web browser. can you please provide details for this.

This is a fantastic article, but I was wondering if there is something similar that can be done for a .NET 4.0 version of the System.Windows.Controls.WebBrowser

Specifically, I would love to be able to trap popups and handle them as illustrated in this article. I am not sure if the 4.0 version of this control has any native support. (But I do know there is no AttachInterfaces method on the updated control, making the above implementation difficult!)

We have a WebBrowser Control application that must automatically download some PDFs. A form must be posted back to the server in order to trigger a PDF download. The name of the PDF file is not known until is arrives at the client.

We need:

• To change the target download save directory programmatically prior to the download.• To have MSIE automatically save the downloaded file to the target directory without the OPEN or SAVE prompt.• A handle to the downloaded file so that it can be renamed programmatically.

I couldn't figure out how to satisfy these requirements with the code you so graciously provided, so I downloaded the C# IE Custom Download Manager from Microsoft. Unfortunately, the download manager is invoked before the download is requested from the server and it is not clear that all of the form input parameters can be accessed from inside IDownloadManager.Download method. Without the input parameters, the form cannot be posted back correctly. Any advice you might give me on how to solve this problem would be appreciated.

I am using ExtendedWebBrowser control. My problem is about page refirecting. Here is the situation.I want to navigate a certain web site but that url is redirected another url. So in this case, ExtendedWebBrowser is locked and can not be capture the redirected url.

I need your help. I want to copy the contents of the web browser control to a rich text box so that it looks the same as it does in the web browser control. If I use the DocumentText property of the web browser control, I get the HTML source which is not what I want. I don't see a web browser control property that's appropriate for this. And it isn't clear that your extended web browser control will enable this; it doesn't look like it. The way I get around this is to select the entire content of the web browser control with the mouse and copy and paste it to the rich text box. This is not very elegant. I'd like to do it with code instead.

Do you have any idea how I can do this?

------------------------------------------------------UPDATE: I figured out how to do this and I'm posting it in case someone else has the same need.

I've tried many Extended Web Browser controls including the "Most Complete Web Browser Control" Unlike all others, this one exposes to .NET many of the more important COM MSHTML interfaces all in .NET. The user does not need to register any COM objects with the system either. It's all ready to go in .NET...

Hi everyone, i'm using an WebBrowser ActiveX Control on InTouch, and start navigating with the instruction #Explorer.Navigate("www.______.com");. When I wont to close the window, I use #Explorer.Quit(); , but receive an error like this: InTouch OLE Automation Error: 0x80020009 Error processing 'Explorer.Quit' on method call (execute). An unspecified error ocurred in the automation object.

Great work! Thanks to Jeroen. I have some problem with a site that after register i can click on a java application that open new java page in "new form". Tiil here that's all right, extwebbr work fine that is it open a new FORM with java application (it NOT OPEN new tab). Now i can use this new form but original page (in extendedwebbrowser) try to refresh a gridview javacontrol and my "new form" go in backgroud. I must click it on taskbar to bring it back. I would want to always maintain this "new form" on top. How can do it? Can i set something form properties on event "befor newwindows2 or newwindows3" is fired? Someone have any suggestion? Thank to all.

I see your code, and is great!, I have some problem to solve, I wants when I make a click don't show me the dialog save or open, I want catch that event and get de URL of the file to download and made on fly a Html to show that file in other tab. can you help me?

I am using the provided Web Browser control in vs2008 and I am getting two script errors when I try to visit www.slacker.com which is an internet radio station. These errors do not allow the player to load. When I tested you program it loaded fine with no errors. What have you added to allow whatever is erroring out in the default control? Your help is greatly appreciated. Thanks

Hi mr Landheervery nice project but i have one little problem with your code!when i open a page that include one object(activex) and this object read vrml(.wrl) file and show it. when i open this file with IE not problem but when use this webbrowser the key event not work and only can work with mouse, is there any way to solve this problem? this object available from http://www.cortona3d.com/cortona[^]and sample available from http://www.parallelgraphics.com/products/cortonace/showcase[^]thanks

I'm using this control to display a particular site over https. The site uses windows authentication, and on login the user is directed to a page with an embedded java applet. The problem is that on some machines, and only some of the time, the browser application crashes, without any error messages (no javascript errors, no windows errors, and nothing written to the event viewer), just after the user submits the authentication form. However, the same user can open IE and access/navigate the site without incident. Occassionally however, following the browser application crash, if the user attempts to access the site using IE, the browser will crash in the same fashion. In all cases, IE6 is used with java 1.4.2_06. It seems that clearing the java cache temporarily fixes the issue. Any thoughts on what may be happening?