File: System\Windows\Forms\Controls\WebBrowser\HtmlShim.cs
Web Access
Project: src\src\System.Windows.Forms\src\System.Windows.Forms.csproj (System.Windows.Forms)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using Windows.Win32.Web.MsHtml;
 
namespace System.Windows.Forms;
 
///  This is essentially a proxy object between the native
///  html objects and our managed ones. We want the managed
///  HtmlDocument, HtmlWindow and HtmlElement to be super-lightweight,
///  which means that we shouldn't have things that tie up their lifetimes
///  contained within them. The "Shim" is essentially the object that
///  manages events coming out of the HtmlDocument, HtmlElement and HtmlWindow
///  and serves them back up to the user.
 
internal abstract class HtmlShim : IDisposable
{
    private EventHandlerList? _events;
    private int _eventCount;
    private Dictionary<EventHandler, HtmlToClrEventProxy>? _attachedEventList;
 
    protected HtmlShim()
    {
    }
 
    private EventHandlerList Events =>
        _events ??= new EventHandlerList();
 
    ///  Support IHtml*3.AttachHandler
    public abstract void AttachEventHandler(string eventName, EventHandler eventHandler);
 
    public void AddHandler(object key, Delegate? value)
    {
        _eventCount++;
        Events.AddHandler(key, value);
        OnEventHandlerAdded();
    }
 
    protected HtmlToClrEventProxy AddEventProxy(string eventName, EventHandler eventHandler)
    {
        _attachedEventList ??= [];
 
        HtmlToClrEventProxy proxy = new(eventName, eventHandler);
        _attachedEventList[eventHandler] = proxy;
        return proxy;
    }
 
    public abstract IHTMLWindow2.Interface? AssociatedWindow { get; }
 
    ///  create connectionpoint cookie
    public abstract void ConnectToEvents();
 
    ///  Support IHtml*3.DetachEventHandler
    public abstract void DetachEventHandler(string eventName, EventHandler eventHandler);
 
    ///  disconnect from connectionpoint cookie
    ///  inheriting classes should override to disconnect from ConnectionPoint and call base.
    public virtual void DisconnectFromEvents()
    {
        if (_attachedEventList is not null)
        {
            EventHandler[] events = new EventHandler[_attachedEventList.Count];
            _attachedEventList.Keys.CopyTo(events, 0);
 
            foreach (EventHandler eh in events)
            {
                HtmlToClrEventProxy proxy = _attachedEventList[eh];
                DetachEventHandler(proxy.EventName, eh);
            }
        }
    }
 
    ///  return the sender for events, usually the HtmlWindow, HtmlElement, HtmlDocument
    protected abstract object GetEventSender();
 
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
 
    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            DisconnectFromEvents();
            _events?.Dispose();
            _events = null;
        }
    }
 
    public void FireEvent(object key, EventArgs e)
    {
        Delegate? delegateToInvoke = Events[key];
 
        if (delegateToInvoke is not null)
        {
            try
            {
                delegateToInvoke.DynamicInvoke(GetEventSender(), e);
            }
            catch (Exception ex)
            {
                // Note: this check is for the debugger, so we can catch exceptions in the debugger instead of
                // throwing a thread exception.
                if (NativeWindow.WndProcShouldBeDebuggable)
                {
                    throw;
                }
                else
                {
                    Application.OnThreadException(ex);
                }
            }
        }
    }
 
    protected virtual void OnEventHandlerAdded()
    {
        ConnectToEvents();
    }
 
    protected virtual void OnEventHandlerRemoved()
    {
        if (_eventCount <= 0)
        {
            DisconnectFromEvents();
            _eventCount = 0;
        }
    }
 
    public void RemoveHandler(object key, Delegate? value)
    {
        _eventCount--;
        Events.RemoveHandler(key, value);
        OnEventHandlerRemoved();
    }
 
    protected HtmlToClrEventProxy? RemoveEventProxy(EventHandler eventHandler)
    {
        if (_attachedEventList is null)
        {
            return null;
        }
 
        if (_attachedEventList.Remove(eventHandler, out HtmlToClrEventProxy? proxy))
        {
            return proxy;
        }
 
        return null;
    }
}