MutationObserver
MutationObserver provides developers a way to react to changes in a
DOM.
using AngleSharp.Attributes;
using System;
using System.Collections.Generic;
namespace AngleSharp.Dom
{
[DomName("MutationObserver")]
public sealed class MutationObserver
{
internal struct MutationOptions
{
public bool IsObservingChildNodes;
public bool IsObservingSubtree;
public bool IsObservingCharacterData;
public bool IsObservingAttributes;
public bool IsExaminingOldCharacterData;
public bool IsExaminingOldAttributeValue;
public IEnumerable<string> AttributeFilters;
public bool IsInvalid {
get {
if (!IsObservingAttributes && !IsObservingCharacterData)
return !IsObservingChildNodes;
return false;
}
}
}
private sealed class MutationObserving
{
private readonly INode _target;
private readonly MutationOptions _options;
private readonly List<INode> _transientNodes;
public INode Target => _target;
public MutationOptions Options => _options;
public List<INode> TransientNodes => _transientNodes;
public MutationObserving(INode target, MutationOptions options)
{
_target = target;
_options = options;
_transientNodes = new List<INode>();
}
}
private readonly Queue<IMutationRecord> _records;
private readonly MutationCallback _callback;
private readonly List<MutationObserving> _observing;
private MutationObserving this[INode node] {
get {
foreach (MutationObserving item in _observing) {
if (item.Target == node)
return item;
}
return null;
}
}
[DomConstructor]
public MutationObserver(MutationCallback callback)
{
if (callback == null)
throw new ArgumentNullException("callback");
_records = new Queue<IMutationRecord>();
_callback = callback;
_observing = new List<MutationObserving>();
}
internal void Enqueue(MutationRecord record)
{
int count = _records.Count;
_records.Enqueue(record);
}
internal void Trigger()
{
IMutationRecord[] array = _records.ToArray();
_records.Clear();
ClearTransients();
if (array.Length != 0)
TriggerWith(array);
}
internal void TriggerWith(IMutationRecord[] records)
{
_callback(records, this);
}
internal MutationOptions ResolveOptions(INode node)
{
foreach (MutationObserving item in _observing) {
if (item.Target == node || item.TransientNodes.Contains(node))
return item.Options;
}
return default(MutationOptions);
}
internal void AddTransient(INode ancestor, INode node)
{
MutationObserving mutationObserving = this[ancestor];
if (mutationObserving != null && mutationObserving.Options.IsObservingSubtree)
mutationObserving.TransientNodes.Add(node);
}
internal void ClearTransients()
{
foreach (MutationObserving item in _observing) {
item.TransientNodes.Clear();
}
}
[DomName("disconnect")]
public void Disconnect()
{
foreach (MutationObserving item in _observing) {
((Node)item.Target).Owner.Mutations.Unregister(this);
}
_records.Clear();
}
[DomName("observe")]
[DomInitDict(1, false)]
public void Connect(INode target, bool childList = false, bool subtree = false, bool? attributes = default(bool?), bool? characterData = default(bool?), bool? attributeOldValue = default(bool?), bool? characterDataOldValue = default(bool?), IEnumerable<string> attributeFilter = null)
{
Node node = target as Node;
if (node != null) {
bool flag = characterDataOldValue ?? false;
bool flag2 = attributeOldValue ?? false;
MutationOptions mutationOptions = default(MutationOptions);
mutationOptions.IsObservingChildNodes = childList;
mutationOptions.IsObservingSubtree = subtree;
mutationOptions.IsExaminingOldCharacterData = flag;
mutationOptions.IsExaminingOldAttributeValue = flag2;
mutationOptions.IsObservingCharacterData = (characterData ?? flag);
mutationOptions.IsObservingAttributes = (attributes ?? (flag2 || attributeFilter != null));
mutationOptions.AttributeFilters = attributeFilter;
MutationOptions mutationOptions2 = mutationOptions;
if (mutationOptions2.IsExaminingOldAttributeValue && !mutationOptions2.IsObservingAttributes)
throw new DomException(DomError.TypeMismatch);
if (mutationOptions2.AttributeFilters != null && !mutationOptions2.IsObservingAttributes)
throw new DomException(DomError.TypeMismatch);
if (mutationOptions2.IsExaminingOldCharacterData && !mutationOptions2.IsObservingCharacterData)
throw new DomException(DomError.TypeMismatch);
if (mutationOptions2.IsInvalid)
throw new DomException(DomError.Syntax);
node.Owner.Mutations.Register(this);
MutationObserving mutationObserving = this[target];
if (mutationObserving != null) {
mutationObserving.TransientNodes.Clear();
_observing.Remove(mutationObserving);
}
_observing.Add(new MutationObserving(target, mutationOptions2));
}
}
[DomName("takeRecords")]
public IEnumerable<IMutationRecord> Flush()
{
while (_records.Count > 0) {
yield return _records.Dequeue();
}
}
}
}