MutationObserver
MutationObserver provides developers a way to react to changes in a
DOM.
using AngleSharp.Attributes;
using AngleSharp.Extensions;
using System;
using System.Collections.Generic;
namespace AngleSharp.Dom
{
[DomName("MutationObserver")]
public sealed class MutationObserver
{
private sealed class MutationObserving
{
private readonly INode _target;
private readonly MutationObserverInit _options;
private readonly List<INode> _transientNodes;
public INode Target => _target;
public MutationObserverInit Options => _options;
public List<INode> TransientNodes => _transientNodes;
public MutationObserving(INode target, MutationObserverInit 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 (object.ReferenceEquals(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 MutationObserverInit ResolveOptions(INode node)
{
foreach (MutationObserving item in _observing) {
if (object.ReferenceEquals(item.Target, node) || item.TransientNodes.Contains(node))
return item.Options;
}
return null;
}
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 node = (Node)item.Target;
node.Owner.Mutations.Unregister(this);
}
_records.Clear();
}
public void Connect(INode target, MutationObserverInit options)
{
Node node = target as Node;
if (node != null) {
if (options == null)
options = new MutationObserverInit();
if (!options.IsExaminingOldCharacterData.HasValue)
options.IsExaminingOldCharacterData = false;
if (!options.IsExaminingOldAttributeValue.HasValue)
options.IsExaminingOldAttributeValue = false;
if (!options.IsObservingAttributes.HasValue)
options.IsObservingAttributes = (options.IsExaminingOldAttributeValue.Value || options.AttributeFilters != null);
if (!options.IsObservingCharacterData.HasValue)
options.IsObservingCharacterData = (options.IsExaminingOldCharacterData.HasValue && options.IsExaminingOldCharacterData.Value);
if (options.IsExaminingOldAttributeValue.Value && !options.IsObservingAttributes.Value)
throw new DomException(DomError.TypeMismatch);
if (options.AttributeFilters != null && !options.IsObservingAttributes.Value)
throw new DomException(DomError.TypeMismatch);
if (options.IsExaminingOldCharacterData.Value && !options.IsObservingCharacterData.Value)
throw new DomException(DomError.TypeMismatch);
if (!options.IsObservingChildNodes && !options.IsObservingCharacterData.Value && !options.IsObservingAttributes.Value)
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, options));
}
}
[DomName("observe")]
public void Connect(INode target, IDictionary<string, object> options)
{
if (options == null)
throw new ArgumentNullException("options");
MutationObserverInit mutationObserverInit = new MutationObserverInit();
mutationObserverInit.AttributeFilters = (options.TryGet("attributeFilter") as IEnumerable<string>);
mutationObserverInit.IsObservingAttributes = options.TryGet<bool>("attributes");
mutationObserverInit.IsObservingChildNodes = (options.TryGet<bool>("childList") ?? false);
mutationObserverInit.IsObservingCharacterData = options.TryGet<bool>("characterData");
mutationObserverInit.IsObservingSubtree = (options.TryGet<bool>("subtree") ?? false);
mutationObserverInit.IsExaminingOldAttributeValue = options.TryGet<bool>("attributeOldValue");
mutationObserverInit.IsExaminingOldCharacterData = options.TryGet<bool>("characterDataOldValue");
MutationObserverInit options2 = mutationObserverInit;
Connect(target, options2);
}
[DomName("takeRecords")]
public IEnumerable<IMutationRecord> Flush()
{
while (_records.Count > 0) {
yield return _records.Dequeue();
}
}
}
}