using AngleSharp.Dom;
using Jint;
using Jint.Native;
namespace XFESystem.Browser;
public sealed partial class DomBridge
{
private JsValue CreateTreeWalker(IDocument owner, JsValue[] arguments)
{
var root = RequireNode(Value(arguments, 0));
uint mask = Value(arguments, 1).IsUndefined() ? uint.MaxValue : Jint.Runtime.TypeConverter.ToUint32(Value(arguments, 1));
var filter = Value(arguments, 2); bool filtering = false; int visits = 0;
FilterResult Accept(INode node)
{
if (++visits > limits.MaximumDomNodes) throw new BrowserLimitException("TreeWalker exceeds its traversal budget.");
if (filter.IsNull() || filter.IsUndefined()) return FilterResult.Accept;
if (filtering) throw AdjacentError("InvalidStateError", "The TreeWalker filter is already active.", 11);
filtering = true; JsValue result;
try { result = CallInternal(bootstrap.Get("filterNode"), [filter, Wrap(node)]); }
catch { filtering = false; throw; }
filtering = false; return (FilterResult)(ushort)result.AsNumber();
}
var walker = owner.CreateTreeWalker(root, unchecked((FilterSettings)mask), Accept);
INode? Previous()
{
// AngleSharp 1.7.1's backward loop does not advance a rejected
// sibling or assign its parent. Use bounded DOM links here.
INode? candidate = walker.Current; int steps = 0;
FilterResult Test(INode node) => (mask & (1u << ((int)node.NodeType - 1))) == 0 ? FilterResult.Skip : Accept(node);
while (candidate is not null && candidate != root)
{
if (++steps > limits.MaximumDomNodes) throw new BrowserLimitException("TreeWalker exceeds its traversal budget.");
if (candidate.PreviousSibling is { } sibling)
{
candidate = sibling; var decision = Test(candidate);
while (decision != FilterResult.Reject && candidate.LastChild is { } child)
{
if (++steps > limits.MaximumDomNodes) throw new BrowserLimitException("TreeWalker exceeds its traversal budget.");
candidate = child; decision = Test(candidate);
}
if (decision == FilterResult.Accept) { walker.Current = candidate; return candidate; }
}
else
{
candidate = candidate.Parent;
if (candidate is not null && Test(candidate) == FilterResult.Accept) { walker.Current = candidate; return candidate; }
}
}
return null;
}
var wrapper = new BrowserHostObject(engine);
CallInternal(bootstrap.Get("initTreeWalker"), [wrapper]);
Property(wrapper, "root", () => Wrap(root)); Property(wrapper, "whatToShow", () => (double)mask);
Property(wrapper, "filter", () => filter.IsUndefined() ? JsValue.Null : filter);
Property(wrapper, "currentNode", () => Wrap(walker.Current), value => walker.Current = RequireNode(value));
void Method(string name, Func<INode?> action) => Function(wrapper, name, _ =>
{
if (filtering) throw AdjacentError("InvalidStateError", "The TreeWalker filter is already active.", 11);
visits = 0; return Wrap(action());
});
Method("nextNode", walker.ToNext); Method("previousNode", Previous); Method("parentNode", walker.ToParent);
Method("firstChild", walker.ToFirst); Method("lastChild", walker.ToLast);
Method("nextSibling", walker.ToNextSibling); Method("previousSibling", walker.ToPreviousSibling);
return wrapper;
}
private JsValue SplitTextNode(IText text, JsValue value)
{
uint offset = Jint.Runtime.TypeConverter.ToUint32(value); string old = text.TextContent;
if (offset > old.Length) throw AdjacentError("IndexSizeError", "The text offset is outside its UTF-16 data.", 1);
// Splitting conserves all characters and adds exactly one node. Admit
// that node before mutation so rejected budgets leave ranges untouched.
ValidateBudget([text.Owner!.CreateTextNode("")]);
var result = text.Split((int)offset);
Changed(text, "characterData", oldValue: old);
if (text.Parent is { } parent) Changed(parent, "childList", added: [result]);
return Wrap(result);
}
}
using AngleSharp.Dom;
using Jint;
using Jint.Native;
namespace XFESystem.Browser;
public sealed partial class DomBridge
{
private JsValue CreateTreeWalker(IDocument owner, JsValue[] arguments)
{
var root = RequireNode(Value(arguments, 0));
uint mask = Value(arguments, 1).IsUndefined() ? uint.MaxValue : Jint.Runtime.TypeConverter.ToUint32(Value(arguments, 1));
var filter = Value(arguments, 2); bool filtering = false; int visits = 0;
FilterResult Accept(INode node)
{
if (++visits > limits.MaximumDomNodes) throw new BrowserLimitException("TreeWalker exceeds its traversal budget.");
if (filter.IsNull() || filter.IsUndefined()) return FilterResult.Accept;
if (filtering) throw AdjacentError("InvalidStateError", "The TreeWalker filter is already active.", 11);
filtering = true; JsValue result;
try { result = CallInternal(bootstrap.Get("filterNode"), [filter, Wrap(node)]); }
catch { filtering = false; throw; }
filtering = false; return (FilterResult)(ushort)result.AsNumber();
}
var walker = owner.CreateTreeWalker(root, unchecked((FilterSettings)mask), Accept);
INode? Previous()
{
// AngleSharp 1.7.1's backward loop does not advance a rejected
// sibling or assign its parent. Use bounded DOM links here.
INode? candidate = walker.Current; int steps = 0;
FilterResult Test(INode node) => (mask & (1u << ((int)node.NodeType - 1))) == 0 ? FilterResult.Skip : Accept(node);
while (candidate is not null && candidate != root)
{
if (++steps > limits.MaximumDomNodes) throw new BrowserLimitException("TreeWalker exceeds its traversal budget.");
if (candidate.PreviousSibling is { } sibling)
{
candidate = sibling; var decision = Test(candidate);
while (decision != FilterResult.Reject && candidate.LastChild is { } child)
{
if (++steps > limits.MaximumDomNodes) throw new BrowserLimitException("TreeWalker exceeds its traversal budget.");
candidate = child; decision = Test(candidate);
}
if (decision == FilterResult.Accept) { walker.Current = candidate; return candidate; }
}
else
{
candidate = candidate.Parent;
if (candidate is not null && Test(candidate) == FilterResult.Accept) { walker.Current = candidate; return candidate; }
}
}
return null;
}
var wrapper = new BrowserHostObject(engine);
CallInternal(bootstrap.Get("initTreeWalker"), [wrapper]);
Property(wrapper, "root", () => Wrap(root)); Property(wrapper, "whatToShow", () => (double)mask);
Property(wrapper, "filter", () => filter.IsUndefined() ? JsValue.Null : filter);
Property(wrapper, "currentNode", () => Wrap(walker.Current), value => walker.Current = RequireNode(value));
void Method(string name, Func<INode?> action) => Function(wrapper, name, _ =>
{
if (filtering) throw AdjacentError("InvalidStateError", "The TreeWalker filter is already active.", 11);
visits = 0; return Wrap(action());
});
Method("nextNode", walker.ToNext); Method("previousNode", Previous); Method("parentNode", walker.ToParent);
Method("firstChild", walker.ToFirst); Method("lastChild", walker.ToLast);
Method("nextSibling", walker.ToNextSibling); Method("previousSibling", walker.ToPreviousSibling);
return wrapper;
}
private JsValue SplitTextNode(IText text, JsValue value)
{
uint offset = Jint.Runtime.TypeConverter.ToUint32(value); string old = text.TextContent;
if (offset > old.Length) throw AdjacentError("IndexSizeError", "The text offset is outside its UTF-16 data.", 1);
// Splitting conserves all characters and adds exactly one node. Admit
// that node before mutation so rejected budgets leave ranges untouched.
ValidateBudget([text.Owner!.CreateTextNode("")]);
var result = text.Split((int)offset);
Changed(text, "characterData", oldValue: old);
if (text.Parent is { } parent) Changed(parent, "childList", added: [result]);
return Wrap(result);
}
}