|
// 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;
}
}
|