使用 C# 在 GeckoFx 中使用 MutationObserver?
Using MutationObserver in GeckoFx with C#?
我正在使用 GeckoFx
登录特定网站。如果登录失败(或需要额外的身份验证,例如 ReCaptcha),本网站将使用新信息编辑页面。不幸的是,我必须在页面更新时访问一个事件。我主要尝试了很多方法
- 在每次登录尝试时持续检查
uri
是否仍然相同,并随后检查有问题的特定元素(以查看 display: none
属性 是否已更改.(这导致了无限循环,因为看起来 GeckoFx
以非阻塞方式更新页面,导致程序进入无限循环)
- 在登录请求和使用上述
uri
检查之间休眠约 5 秒。所有这一切(不出所料,我正在抓住救命稻草)是冻结浏览器 5 秒,仍然无法更新页面
- 在页面更新时搜索特定事件的
GeckoFx
代码库,类似于 DocumentCompleted
事件(没有这样的运气)。
我读到的最常见的方法(也是最有意义的方法)是使用 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);
我似乎无法在 GeckoFx
的 MutationObserver
来源中找到任何允许我设置 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())
我正在使用 GeckoFx
登录特定网站。如果登录失败(或需要额外的身份验证,例如 ReCaptcha),本网站将使用新信息编辑页面。不幸的是,我必须在页面更新时访问一个事件。我主要尝试了很多方法
- 在每次登录尝试时持续检查
uri
是否仍然相同,并随后检查有问题的特定元素(以查看display: none
属性 是否已更改.(这导致了无限循环,因为看起来GeckoFx
以非阻塞方式更新页面,导致程序进入无限循环) - 在登录请求和使用上述
uri
检查之间休眠约 5 秒。所有这一切(不出所料,我正在抓住救命稻草)是冻结浏览器 5 秒,仍然无法更新页面 - 在页面更新时搜索特定事件的
GeckoFx
代码库,类似于DocumentCompleted
事件(没有这样的运气)。
我读到的最常见的方法(也是最有意义的方法)是使用 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);
我似乎无法在 GeckoFx
的 MutationObserver
来源中找到任何允许我设置 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())