using AngleSharp.Dom; using Jint; using Jint.Native; using Jint.Runtime; namespace XFESystem.Browser; public sealed partial class DomBridge { private static AdjacentPosition AdjacentPositionOf(string position) => position.ToLowerInvariant() switch { "beforebegin" => AdjacentPosition.BeforeBegin, "afterbegin" => AdjacentPosition.AfterBegin, "beforeend" => AdjacentPosition.BeforeEnd, "afterend" => AdjacentPosition.AfterEnd, _ => throw new ArgumentException("Invalid adjacent insertion position.") }; private AdjacentPosition ParseAdjacentPosition(string position) { try { return AdjacentPositionOf(position); } catch (ArgumentException) { throw AdjacentError("SyntaxError", "Invalid adjacent insertion position.", 12); } } private static (INode? Parent, INode? Before) AdjacentDestination(IElement element, AdjacentPosition position) => position switch { AdjacentPosition.BeforeBegin => (element.Parent, element), AdjacentPosition.AfterBegin => (element, element.FirstChild), AdjacentPosition.BeforeEnd => (element, null), AdjacentPosition.AfterEnd => (element.Parent, element.NextSibling), _ => throw new ArgumentOutOfRangeException(nameof(position)) }; private void InsertAdjacentHtml(IElement element, string position, JsValue value) { var where = ParseAdjacentPosition(position); var (parent, before) = AdjacentDestination(element, where); if (parent is null || parent.NodeType == NodeType.Document) throw AdjacentError("NoModificationAllowedError", "HTML insertion requires a parent other than Document.", 7); // HTML fragment parsing uses the actual surrounding element (table/select/SVG included). // The synthetic body context is required when the insertion context is an HTML html node. var context = parent as IElement; if (context is null || context.LocalName == "html" && context.NamespaceUri == "http://www.w3.org/1999/xhtml") context = document.CreateElement("body"); var markup = LimitedText(value); var fragment = document.CreateDocumentFragment(); // AngleSharp 1.7.1 HtmlParser.ParseFragment recreates its context as an HTML // element, losing SVG/MathML namespaces. Element.Insert's fragment parser uses // the actual context, so parse into an unobserved shallow clone first. var staging = (IElement)context.Clone(false); staging.Insert(AdjacentPosition.BeforeEnd, markup); foreach (var node in staging.ChildNodes.ToArray()) fragment.AppendChild(node); var added = fragment.ChildNodes.ToArray(); if (added.Length == 0) return; // Budget the complete fragment before changing the live tree. A rejected insertion // cannot leave an early sibling behind, change existing identities, or queue resources. ValidateAdjacentInsertion(parent, added); parent.InsertBefore(fragment, before); Changed(parent, "childList", added: added); // Parsed scripts remain inert, matching innerHTML. Stylesheet links are page-owned work. foreach (var node in added) if (Connected(node)) foreach (var link in SelfAndDescendants(node).Where(child => child.LocalName == "link")) host.ResourceRequested?.Invoke(link); } private void InsertAdjacentText(IElement element, string position, JsValue value) { var (parent, before) = AdjacentDestination(element, ParseAdjacentPosition(position)); var text = LimitedText(value); if (parent is null) return; var node = document.CreateTextNode(text); ValidateAdjacentInsertion(parent, [node]); Insert(parent, node, before); } private JsValue InsertAdjacentElement(IElement element, string position, JsValue value) { var where = ParseAdjacentPosition(position); var child = value.IsObject() && nodes.TryGetValue(value.AsObject(), out var known) ? known : null; if (child is not IElement) throw AdjacentError("TypeError", "insertAdjacentElement requires an Element.", 0); var (parent, before) = AdjacentDestination(element, where); // Unlike HTML insertion, these methods use insert-adjacent's null-parent return. if (parent is null) return JsValue.Null; return Insert(parent, child, before); } private void ValidateAdjacentInsertion(INode parent, INode[] added) { ValidateBudget(added); var parentDepth = 0; for (var ancestor = parent.Parent; ancestor is not null; ancestor = ancestor.Parent) ++parentDepth; var pending = new Stack<(INode Node, int Depth)>(); foreach (var node in added) pending.Push((node, parentDepth + 1)); while (pending.TryPop(out var entry)) { if (entry.Depth > limits.MaximumDomDepth) throw new BrowserLimitException("The insertion exceeds the DOM depth limit."); foreach (var node in entry.Node.ChildNodes) pending.Push((node, entry.Depth + 1)); } var projected = added.Prepend(parent).Distinct(ReferenceEqualityComparer.Instance).ToArray(); if (wrappers.Count + projected.Count(node => !wrappers.ContainsKey(node)) > limits.MaximumDomNodes || identities.Count + projected.Count(node => !identities.ContainsKey(node)) > limits.MaximumDomNodes) throw new BrowserLimitException("The insertion exceeds the DOM wrapper or identity limit."); } private JavaScriptException AdjacentError(string name, string message, int code) { var value = new BrowserHostObject(engine); Data(value, "name", name); Data(value, "message", message); Data(value, "code", code); return new JavaScriptException(value); } }