How to get around the memory leak in the .NET Webbrowser control?

Solution 1:

This leak appears to be a leak in unmanaged memory, so nothing you do in your process is going to reclaim that memory. From your post I can see that you've tried avoid the leak quite extensively and without success.

I would suggest a different approach if feasible. Create a separate application that uses web browser control and start it from your application. Use the method described here to embed newly created application within your own existing application. Communicate with that application using WCF or .NET remoting. Restart the child process from time to time to prevent it from taking to much memory.

This of course is quite complicated solution and the restart process could probably look ugly. You might maybe resort to restarting the whole browser application every time user navigates to another page.

Solution 2:

I took the udione's code (it worked for me, thanks!) and changed two small things:

  1. IKeyboardInputSite is a public interface and has method Unregister(), so we don't need to use reflection after we received a reference to *_keyboardInputSinkChildren* collection.

  2. As view not always has a direct reference to its window class (especially in MVVM) I added a method GetWindowElement(DependencyObject element) which returns the required reference by traversing through visual tree.

Thanks, udione

public void Dispose()
{
    _browser.Dispose();

    var window = GetWindowElement(_browser);

    if (window == null)
        return;

    var field = typeof(Window).GetField("_swh", BindingFlags.NonPublic | BindingFlags.Instance);

    var valueSwh = field.GetValue(window);
    var valueSourceWindow = valueSwh.GetType().GetField("_sourceWindow", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(valueSwh);
    var valuekeyboardInput = valueSourceWindow.GetType().GetField("_keyboardInputSinkChildren", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(valueSourceWindow);

    var inputSites = valuekeyboardInput as IEnumerable<IKeyboardInputSite>;

    if (inputSites == null)
        return;

    var currentSite = inputSites.FirstOrDefault(s => ReferenceEquals(s.Sink, _browser));

    if (currentSite != null)
        currentSite.Unregister();
}

private static Window GetWindowElement(DependencyObject element)
{
    while (element != null && !(element is Window))
    {
        element = VisualTreeHelper.GetParent(element);
    }

    return element as Window;
}

Thank you all!

Solution 3:

There is a way to do clear memory leaks by using reflection and removing references from private fields on mainForm. This is not a good solution but for desperate people here is the code:

//dispose to clear most of the references
this.webbrowser.Dispose();
BindingOperations.ClearAllBindings(this.webbrowser);

//using reflection to remove one reference that was not removed with the dispose 
var field = typeof(System.Windows.Window).GetField("_swh", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

var valueSwh = field.GetValue(mainwindow);

var valueSourceWindow = valueSwh.GetType().GetField("_sourceWindow", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(valueSwh);

var valuekeyboardInput = valueSourceWindow.GetType().GetField("_keyboardInputSinkChildren", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(valueSourceWindow);

System.Collections.IList ilist = valuekeyboardInput as System.Collections.IList;

lock(ilist)
{
    for (int i = ilist.Count-1; i >= 0; i--)
    {
        var entry = ilist[i];
        var sinkObject = entry.GetType().GetField("_sink", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        if (object.ReferenceEquals(sinkObject.GetValue(entry), this.webbrowser.webBrowser))
        {
            ilist.Remove(entry);
        }
    }
} 

Solution 4:

Having battled this exact Out of Memory issue from different directions (Win32 WorkingSet / COM SHDocVw interfaces, etc.) with the WPF WebBrowser component, I discovered the problem for us was jqGrid plugin holding onto unmanaged resources in the IE ActiveXHost and not releasing them after calling WebBrowser.Dispose(). This issue is often created by Javascript that is not behaving properly. The odd thing is that the Javascript works fine in regular IE - just not from within the WebBrowser control. I surmise that the garbage collection is different between the two integration points as IE can never really be closed.

One thing I'd suggest if you are authoring the source pages is to remove all JS components and slowly adding them back in. Once you identify the offending JS plugin (like we did) - it should be easy to remedy the problem. In our case we just used $("#jqgrid").jqGrid('GridDestroy') to properly remove the events and associated DOM elements it created. This took care of the issue for us by invoking this when the browser is closed via WebBrowser.InvokeScript.

If you don't have the ability to modify the source pages you navigate to - you would have to inject some JS into the page to clean up the DOM events and elements that are leaking memory. It would be nice if Microsoft found a resolution to this, but for now we are left probing for JS plugins that need cleansed.