使用 C# 在 GeckoFx 中使用 MutationObserver?

Using MutationObserver in GeckoFx with C#?

我正在使用 GeckoFx 登录特定网站。如果登录失败(或需要额外的身份验证,例如 ReCaptcha),本网站将使用新信息编辑页面。不幸的是,我必须在页面更新时访问一个事件。我主要尝试了很多方法

我读到的最常见的方法(也是最有意义的方法)是使用 MutationObserver。似乎互联网上的所有答案都涉及注入 Javascript 以执行必要的任务。鉴于我所有的编程背景都没有接触过任何网络开发,我正在努力坚持我所知道的。

到目前为止,这是我的方法,不幸的是,它并不多。

public class GeckoTestWebLogin
{
    private readonly string _user;
    private readonly string _pass;
    public GeckoWebBrowser Gweb;
    public Uri LoginUri { get; } = new Uri("https://website.com/login/");

    public bool LoginCompleted { get; private set; } = false;
    public bool Loaded { get; private set; } = false;

    public GeckoTestWebLogin(string user, string pass)
    {
        _user = user;
        _pass = pass;
        Xpcom.EnableProfileMonitoring = false;
        Xpcom.Initialize("Firefox");

        //this code is for testing purposes, it will be removed upon project completion
        CookieManager.RemoveAll();

        Gweb = new GeckoWebBrowser();
        Gweb.DocumentCompleted += DocLoaded;

        //right about here is where I get lost, where can I set a callback method for the observer to report back to? Is this even how it works?
        MutationObserver mutationObserver = new MutationObserver(Gweb.Window.DomWindow, (nsISupports)Gweb.Document.DomObject);
    }

    private void TestObservedEvent(string parms, object[] objs)
    {
        MessageBox.Show("The page was changed @ " + DateTime.Now);
    }

    public void DocLoaded(object obj, GeckoDocumentCompletedEventArgs e)
    {
        Loaded = true;
        if (Gweb.Url != LoginUri) return;
        AttemptLogin();
    }

    private void AttemptLogin()
    {
        GeckoElementCollection elements = Gweb.Document.GetElementsByTagName("input");
        foreach (GeckoHtmlElement element in elements)
        {
            switch (element.Id)
            {
                case "username":
                    element.SetAttribute("value", _user);
                    break;
                case "password":
                    element.SetAttribute("value", _pass);
                    break;
                case "importantchangedinfo":
                    GeckoHtmlElement authcodeModal =
                        (GeckoHtmlElement)
                            Gweb.Document.GetElementsByClassName("login_modal").First();
                    if (authcodeModal.Attributes["style"].NodeValue != "display: none")
                    {
                        InputForm form = new InputForm { InputDescription = "Captcha Required!" };
                        form.ShowDialog();
                        elements.FirstOrDefault(x => x.Id == "captchabox")?.SetAttribute("value", form.Input);
                    }
                    break;
            }
        }
        elements.FirstOrDefault(x => x.Id == "Login")?.Click();
    }

    public void Login()
    {
        //this will cause the DocLoaded event to fire after completion
        Gweb.Navigate(LoginUri.ToString());
    }
}

如评论中的上述代码,我完全迷失在

MutationObserver mutationObserver = new MutationObserver(Gweb.Window.DomWindow, (nsISupports)Gweb.Document.DomObject);

我似乎无法在 GeckoFxMutationObserver 来源中找到任何允许我设置 callback/event/whathaveyou 的内容。我的方法是处理事情的正确方法还是除了将 Javascript 注入页面之外我别无选择?

非常感谢,提前致谢。

这是我在 Tom 的回答中对选项 2 的尝试:

(添加到 GeckoTestWebLogin)

    public void DocLoaded(object obj, GeckoDocumentCompletedEventArgs e)
    {
        Loaded = true;
        if (Gweb.Url != LoginUri) return;

        MutationEventListener mutationListener = new MutationEventListener();
        mutationListener.OnDomMutation += TestObservedEvent;
        nsIDOMEventTarget target = Xpcom.QueryInterface<nsIDOMEventTarget>(/*Lost here*/);
        using (nsAString modified = new nsAString("DOMSubtreeModified"))
            target.AddEventListener(modified, mutationListener, true, false, 0);

        AttemptLogin();
    }

MutationEventListener.cs:

public delegate void OnDomMutation(/*DomMutationArgs args*/);

public class MutationEventListener : nsIDOMEventListener
{
    public event OnDomMutation OnDomMutation;

    public void HandleEvent(nsIDOMEvent domEvent)
    {
        OnDomMutation?.Invoke(/*new DomMutationArgs(domEvent, this)*/);
    }
}

我认为 Geckofx 的 webidl compiler 目前还不够先进,无法生成回调构造函数。

选项 1。- 增强 MutationObserver 源。

您可以手动修改 MutationObserver 源以添加必要的构造函数回调。然后重新编译 Geckofx。 (我还没看这有多难)

选项 2。- 使用旧式 Mutation 事件。

public class DOMSubtreeModifiedEventListener : nsIDOMEventListener
{
     ... // Implement HandleEvent 
}

然后类似(可能在 DocumentCompleted 事件处理程序中):

    _domSubtreeModifiedEventListener = new DOMSubtreeModifiedEventListener(this);

    var target = Xpcom.QueryInterface<nsIDOMEventTarget>(body);
    using (nsAString subtreeModified = new nsAString("DOMSubtreeModified"))
        target.AddEventListener(subtreeModified, _domSubtreeModifiedEventListener, true, false, 0);

选项 3。- 使用空闲 + 检查。

添加 winforms Application.idle 事件处理程序 - 并检查文档,了解其何时准备就绪。

选项 4。- 注入一个 javascript 回调。

(正如您已经提到的)- 此示例等待调整大小完成后。

基本上注入:"<body onresize=fireResizedEventAfterDelay()>" : 然后注入这样的东西:

 string fireResizedEventAfterDelayScript = "<script>\n" +
            "var resizeListner;" +
            "var msDelay = 20;" +
            "function fireResizedEventAfterDelay() {" +
            "clearTimeout(resizeListner);" +
            "resizeListner = setTimeout(function() { document.dispatchEvent (new MessageEvent('resized')); }, msDelay);" +
            "}\n" +
            "</script>\n";

然后在 C# 中:

browser.AddMessageEventListener("resized", (s) => runafterImDone())