XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
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<INode>(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);
    }
}