如何在WebBrowser控件中注入Javascript?

83

我已经尝试过这个:

string newScript = textBox1.Text;
HtmlElement head = browserCtrl.Document.GetElementsByTagName("head")[0];
HtmlElement scriptEl = browserCtrl.Document.CreateElement("script");
lblStatus.Text = scriptEl.GetType().ToString();
scriptEl.SetAttribute("type", "text/javascript");
head.AppendChild(scriptEl);
scriptEl.InnerHtml = "function sayHello() { alert('hello') }";
和都会引发错误。
System.NotSupportedException: Property is not supported on this type of HtmlElement.
   at System.Windows.Forms.HtmlElement.set_InnerHtml(String value)
   at SForceApp.Form1.button1_Click(Object sender, EventArgs e) in d:\jsight\installs\SForceApp\SForceApp\Form1.cs:line 31
   at System.Windows.Forms.Control.OnClick(EventArgs e)
   at System.Windows.Forms.Button.OnClick(EventArgs e)
   at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.ButtonBase.WndProc(Message& m)
   at System.Windows.Forms.Button.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

有没有一种简单的方法将脚本注入到DOM中?

16个回答

102

由于某些原因,Richard的解决方案在我的环境中无法工作(insertAdjacentText引发异常)。然而,这种方法似乎可以解决问题:

HtmlElement head = webBrowser1.Document.GetElementsByTagName("head")[0];
HtmlElement scriptEl = webBrowser1.Document.CreateElement("script");
IHTMLScriptElement element = (IHTMLScriptElement)scriptEl.DomElement;
element.text = "function sayHello() { alert('hello') }";
head.AppendChild(scriptEl);
webBrowser1.Document.InvokeScript("sayHello");

这个答案解释了如何将IHTMLScriptElement接口引入到您的项目中。


1
很酷,我认为在任何情况下使用IHTMLScriptElement可以使代码意图更加明显。我确实想知道为什么你会得到一个异常,但是在COM互操作方面这就是生活。 - ZeroBugBounce
添加一个指向本地 js 文件的引用怎么样?请参见 https://dev59.com/CW865IYBdhLWcg3wBJ0q。 - IAmAN00B
请帮我解决这个问题。你是如何做到的?我想通过我的C++应用程序实时将JS注入到我的页面中。我该怎么做呢? - Johnydep
这适用于注入jQuery文件吗? - gumuruh

52
HtmlDocument doc = browser.Document;
HtmlElement head = doc.GetElementsByTagName("head")[0];
HtmlElement s = doc.CreateElement("script");
s.SetAttribute("text","function sayHello() { alert('hello'); }");
head.AppendChild(s);
browser.Document.InvokeScript("sayHello");

(在 .NET 4 / Windows Forms 应用程序中进行测试)

编辑:修复了函数 set 中的大小写问题。


12
我很喜欢这个解决方案,因为它不依赖于(虽然很有用!)IHTMLSCriptElement程序集。 - Micah Smith
6
这应该是被接受的答案。它与当前答案相同,但更简单,且不依赖于“IHTMLScriptElement”。 - BlueRaja - Danny Pflughoeft

33

在我努力研究之后,这是我发现的最简单方法:

string javascript = "alert('Hello');";
// or any combination of your JavaScript commands
// (including function calls, variables... etc)

// WebBrowser webBrowser1 is what you are using for your web browser
webBrowser1.Document.InvokeScript("eval", new object[] { javascript });

eval(str) 是一个全局的JavaScript函数,其作用是解析并执行 str 中写入的任何内容。可以参考 w3schools 的说明


22

此外,在.NET 4中,如果您使用动态关键字,这将变得更加容易:

dynamic document = this.browser.Document;
dynamic head = document.GetElementsByTagName("head")[0];
dynamic scriptEl = document.CreateElement("script");
scriptEl.text = ...;
head.AppendChild(scriptEl);

3
为什么需要动态类型呢?如果你想要节省打字的时间,C# 3.0 版本以后就支持类型推断,使用 var 关键字也是可以的。没有必要开始调用 DLR(动态语言运行时)。 - alimbada
23
这基本上就是动态关键字的用途:COM互操作。这里实际上没有类型推断,只有文档说明。例如,IHTMLElement2不能从IHtmlElement分配,而在运行时您只有一个COM代理对象。你只需要知道要将哪些接口转换成什么。动态关键字帮助你减少很多冗长的代码。你知道方法存在,为什么要将其转换为某个接口?它并不完全“调用DLR”,它只生成知道如何在COM对象上调用方法的代码(在这种情况下)。 - justin.m.chase
1
@justin.m.chase 你救了我的命。 - Khizar Iqbal

17

如果你只是想运行JavaScript,这将是最简单的方法(VB .Net):

MyWebBrowser.Navigate("javascript:function foo(){alert('hello');}foo();")

我猜这不会“注入”它,但如果你想要运行你的函数,它会运行它。 (以防你把问题搞得太复杂)如果你能想出如何在javascript中注入,请将其放入函数“foo”的主体中,并让javascript为你执行注入。


10

托管的 HTML 文档包装器没有完全实现您所需的功能,因此您需要深入了解 MSHTML API 以完成您想要的操作:

1)添加对 MSHTML 的引用,在 COM 引用下可能会被称为“Microsoft HTML 对象库”。

2)在命名空间中添加 'using mshtml;'。

3)获取对脚本元素的 IHTMLElement 的引用:

IHTMLElement iScriptEl = (IHTMLElement)scriptEl.DomElement;

4) 调用insertAdjacentText方法,第一个参数值为"afterBegin"。所有可能的值在这里列出:

iScriptEl.insertAdjacentText("afterBegin", "function sayHello() { alert('hello') }");

5) 现在您将能够在 scriptEl.InnerText 属性中看到代码。

Hth, Richard


1
不错...这个加上korchev提供的提示完美解决了。我希望我能在这一个问题上设置两个被接受的解决方案。 :) - jsight

9
我认为从C#向WebBrowser控件HTML文档中注入JavaScript的最简单方法是使用"execScript"方法,并将要注入的代码作为参数调用该方法。
在此示例中,JavaScript代码被注入并在全局范围内执行:
var jsCode="alert('hello world from injected code');";
WebBrowser.Document.InvokeScript("execScript", new Object[] { jsCode, "JavaScript" });

如果您想延迟执行,请注入函数并在之后调用它们:

var jsCode="function greet(msg){alert(msg);};";
WebBrowser.Document.InvokeScript("execScript", new Object[] { jsCode, "JavaScript" });
...............
WebBrowser.Document.InvokeScript("greet",new object[] {"hello world"});

这适用于Windows Forms和WPF WebBrowser控件。

这个解决方案不是跨浏览器的,因为"execScript"仅在IE和Chrome中定义。但是问题是关于Microsoft WebBrowser控件的,而IE是唯一支持的。

要想有效地跨浏览器注入javascript代码,可以使用new关键字创建一个Function对象。下面的示例创建了一个带有注入代码的匿名函数,并执行它(javascript实现了闭包,该函数可以访问全局空间,而不会污染局部变量)。

var jsCode="alert('hello world');";
(new Function(code))();

当然,您可以延迟执行:
var jsCode="alert('hello world');";
var inserted=new Function(code);
.................
inserted();

希望这有所帮助。

8
作为对被接受的答案的跟进,这是一个最小化的IHTMLScriptElement接口定义,不需要包含额外的类型库:
[ComImport, ComVisible(true), Guid(@"3050f28b-98b5-11cf-bb82-00aa00bdce0b")]
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)]
[TypeLibType(TypeLibTypeFlags.FDispatchable)]
public interface IHTMLScriptElement
{
    [DispId(1006)]
    string text { set; [return: MarshalAs(UnmanagedType.BStr)] get; }
}

因此,WebBrowser控件派生类中的完整代码如下:

protected override void OnDocumentCompleted(
    WebBrowserDocumentCompletedEventArgs e)
{
    base.OnDocumentCompleted(e);

    // Disable text selection.
    var doc = Document;
    if (doc != null)
    {
        var heads = doc.GetElementsByTagName(@"head");
        if (heads.Count > 0)
        {
            var scriptEl = doc.CreateElement(@"script");
            if (scriptEl != null)
            {
                var element = (IHTMLScriptElement)scriptEl.DomElement;
                element.text =
                    @"function disableSelection()
                    { 
                        document.body.onselectstart=function(){ return false; }; 
                        document.body.ondragstart=function() { return false; };
                    }";
                heads[0].AppendChild(scriptEl);
                doc.InvokeScript(@"disableSelection");
            }
        }
    }
}

7

这是一个使用mshtml的解决方案。

IHTMLDocument2 doc = new HTMLDocumentClass();
doc.write(new object[] { File.ReadAllText(filePath) });
doc.close();

IHTMLElement head = (IHTMLElement)((IHTMLElementCollection)doc.all.tags("head")).item(null, 0);
IHTMLScriptElement scriptObject = (IHTMLScriptElement)doc.createElement("script");
scriptObject.type = @"text/javascript";
scriptObject.text = @"function btn1_OnClick(str){
    alert('you clicked' + str);
}";
((HTMLHeadElementClass)head).appendChild((IHTMLDOMNode)scriptObject);

2
由于这是一个社区资源,他的答案仍然对其他人(比如我自己)有用。+1 对于mshtml等效物,帮我省去了一个问题。 - Kyeotic
1
对于任何发现此问题的人,请从最后一行中删除“class”,应该读作((HTMLHeadElement) head).appendChild((IHTMLDOMNode) scriptObject);否则会出现错误。 - Kyeotic
1
这个答案能否被管理员提升一下呢?以上的答案在实际情况中都是错误的。这个虽然晚来了,但确实是最正确的答案。 - justin.m.chase

2
我用了这个:D
HtmlElement script = this.WebNavegador.Document.CreateElement("SCRIPT");
script.SetAttribute("TEXT", "function GetNameFromBrowser() {" + 
"return 'My name is David';" + 
"}");

this.WebNavegador.Document.Body.AppendChild(script);

然后,您可以执行并通过以下方式获取结果:
string myNameIs = (string)this.WebNavegador.Document.InvokeScript("GetNameFromBrowser");

I hope to be helpful


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接