使用C#处理WebBrowser控件在不同域名中的跨域问题
程序员文章站
2024-03-05 22:39:01
我们在做web测试时,经常会使用webbrowser来进行一些自动化的任务。而有些网页上面会用iframe去嵌套别的页面,这些页面可能不是在相同域名下的,这时就会出现跨域问...
我们在做web测试时,经常会使用webbrowser来进行一些自动化的任务。而有些网页上面会用iframe去嵌套别的页面,这些页面可能不是在相同域名下的,这时就会出现跨域问题,无法直接在webbrowser中获取到iframe中的元素。下面来做个试验,自己写个页面嵌套一个百度的首页,然后在我们自己的页面上输入要查询的词,最后在百度上自动完成搜索。
复制代码 代码如下:
<!doctype html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<iframe id="baidu" style="float:left;" width="500" height="500" src="http://www.baidu.com"></iframe>
<div>
测试值:<input id="search" type="text" />
</div>
</body>
</html>
下面再建一个简单的winform工程测试一下,界面如下:
下面就是webbrowser的测试代码:
复制代码 代码如下:
using system;
using system.windows.forms;
namespace webbrowsertest
{
public partial class form1 : form
{
public form1()
{
initializecomponent();
}
private void button1_click(object sender, eventargs e)
{
this.webbrowser1.navigate(this.textbox1.text);
}
private void button2_click(object sender, eventargs e)
{
var doc = this.webbrowser1.document;
var frames = doc.window.frames;
string testvalue = doc.getelementbyid("search").getattribute("value");
frames[0].document.getelementbyid("kw").setattribute("value", testvalue);
frames[0].document.getelementbyid("su").invokemember("click");
}
}
}
我们运行我们的测试程序后,加载之前我们自己写的页面后,在自己的页面上输入我们要查询的词,点击测试按钮,就会看到程序报未处理 unauthorizedaccessexception错误:
下面来编写一个helper类来解决这个问题,主要原理大致就是利用iwebbrowser2这个接口来获取ifream中的dom,iwebbrowser2中的document可以转换为ihtmldocument1,ihtmldocument2,ihtmldocument3。
复制代码 代码如下:
using system;
using system.runtime.interopservices;
using system.windows.forms;
using mshtml;
namespace webbrowsertest
{
// this is the com iserviceprovider interface, not system.iserviceprovider .net interface!
[comimport(), comvisible(true), guid("6d5140c1-7436-11ce-8034-00aa006009fa"),
interfacetypeattribute(cominterfacetype.interfaceisiunknown)]
public interface iserviceprovider
{
[return: marshalas(unmanagedtype.i4)]
[preservesig]
int queryservice(ref guid guidservice, ref guid riid, [marshalas(unmanagedtype.interface)] out object ppvobject);
}
public enum olecmdf
{
olecmdf_defhideonctxtmenu = 0x20,
olecmdf_enabled = 2,
olecmdf_invisible = 0x10,
olecmdf_latched = 4,
olecmdf_ninched = 8,
olecmdf_supported = 1
}
public enum olecmdid
{
olecmdid_pagesetup = 8,
olecmdid_print = 6,
olecmdid_printpreview = 7,
olecmdid_properties = 10,
olecmdid_saveas = 4
}
public enum olecmdexecopt
{
olecmdexecopt_dodefault,
olecmdexecopt_promptuser,
olecmdexecopt_dontpromptuser,
olecmdexecopt_showhelp
}
[comimport, guid("d30c1661-cdaf-11d0-8a3e-00c04fc9e26e"), typelibtype(typelibtypeflags.foleautomation | typelibtypeflags.fdual | typelibtypeflags.fhidden)]
public interface iwebbrowser2
{
[dispid(100)]
void goback();
[dispid(0x65)]
void goforward();
[dispid(0x66)]
void gohome();
[dispid(0x67)]
void gosearch();
[dispid(0x68)]
void navigate([in] string url, [in] ref object flags, [in] ref object targetframename, [in] ref object postdata, [in] ref object headers);
[dispid(-550)]
void refresh();
[dispid(0x69)]
void refresh2([in] ref object level);
[dispid(0x6a)]
void stop();
[dispid(200)]
object application { [return: marshalas(unmanagedtype.idispatch)] get; }
[dispid(0xc9)]
object parent { [return: marshalas(unmanagedtype.idispatch)] get; }
[dispid(0xca)]
object container { [return: marshalas(unmanagedtype.idispatch)] get; }
[dispid(0xcb)]
object document { [return: marshalas(unmanagedtype.idispatch)] get; }
[dispid(0xcc)]
bool toplevelcontainer { get; }
[dispid(0xcd)]
string type { get; }
[dispid(0xce)]
int left { get; set; }
[dispid(0xcf)]
int top { get; set; }
[dispid(0xd0)]
int width { get; set; }
[dispid(0xd1)]
int height { get; set; }
[dispid(210)]
string locationname { get; }
[dispid(0xd3)]
string locationurl { get; }
[dispid(0xd4)]
bool busy { get; }
[dispid(300)]
void quit();
[dispid(0x12d)]
void clienttowindow(out int pcx, out int pcy);
[dispid(0x12e)]
void putproperty([in] string property, [in] object vtvalue);
[dispid(0x12f)]
object getproperty([in] string property);
[dispid(0)]
string name { get; }
[dispid(-515)]
int hwnd { get; }
[dispid(400)]
string fullname { get; }
[dispid(0x191)]
string path { get; }
[dispid(0x192)]
bool visible { get; set; }
[dispid(0x193)]
bool statusbar { get; set; }
[dispid(0x194)]
string statustext { get; set; }
[dispid(0x195)]
int toolbar { get; set; }
[dispid(0x196)]
bool menubar { get; set; }
[dispid(0x197)]
bool fullscreen { get; set; }
[dispid(500)]
void navigate2([in] ref object url, [in] ref object flags, [in] ref object targetframename, [in] ref object postdata, [in] ref object headers);
[dispid(0x1f5)]
olecmdf querystatuswb([in] olecmdid cmdid);
[dispid(0x1f6)]
void execwb([in] olecmdid cmdid, [in] olecmdexecopt cmdexecopt, ref object pvain, intptr pvaout);
[dispid(0x1f7)]
void showbrowserbar([in] ref object pvaclsid, [in] ref object pvarshow, [in] ref object pvarsize);
[dispid(-525)]
webbrowserreadystate readystate { get; }
[dispid(550)]
bool offline { get; set; }
[dispid(0x227)]
bool silent { get; set; }
[dispid(0x228)]
bool registerasbrowser { get; set; }
[dispid(0x229)]
bool registerasdroptarget { get; set; }
[dispid(0x22a)]
bool theatermode { get; set; }
[dispid(0x22b)]
bool addressbar { get; set; }
[dispid(0x22c)]
bool resizable { get; set; }
}
class corssdomainhelper
{
private static guid iid_iwebbrowserapp = new guid("0002df05-0000-0000-c000-000000000046");
private static guid iid_iwebbrowser2 = new guid("d30c1661-cdaf-11d0-8a3e-00c04fc9e26e");
// utility for ie cross domain access
// returns null in case of failure.
public static ihtmldocument3 getdocumentfromwindow(ihtmlwindow2 htmlwindow)
{
if (htmlwindow == null)
{
return null;
}
// first try the usual way to get the document.
try
{
ihtmldocument2 doc = htmlwindow.document;
return (ihtmldocument3)doc;
}
catch (comexception comex)
{
// i think comexception won't be ever fired but just to be sure ...
}
catch (unauthorizedaccessexception)
{
}
catch (exception ex)
{
return null;
}
// at this point the error was e_accessdenied because the frame contains a document from another domain.
// ie tries to prevent a cross frame scripting security issue.
try
{
// convert ihtmlwindow2 to iwebbrowser2 using iserviceprovider.
iserviceprovider sp = (iserviceprovider)htmlwindow;
// use iserviceprovider.queryservice to get iwebbrowser2 object.
object brws = null;
sp.queryservice(ref iid_iwebbrowserapp, ref iid_iwebbrowser2, out brws);
// get the document from iwebbrowser2.
iwebbrowser2 browser = (iwebbrowser2)(brws);
return (ihtmldocument3)browser.document;
}
catch (exception ex)
{
console.writeline(ex);
}
return null;
}
}
}
最后将我们的运行代码改为如下形式,调用helper类中的getdocumentfromwindow方法:
复制代码 代码如下:
using system;
using system.windows.forms;
using mshtml;
namespace webbrowsertest
{
public partial class form1 : form
{
public form1()
{
initializecomponent();
}
private void button1_click(object sender, eventargs e)
{
this.webbrowser1.navigate(this.textbox1.text);
}
private void button2_click(object sender, eventargs e)
{
var doc = this.webbrowser1.document;
var frames = doc.window.frames;
string testvalue = doc.getelementbyid("search").getattribute("value");
ihtmldocument3 baidudoc = corssdomainhelper.getdocumentfromwindow(frames[0].domwindow as ihtmlwindow2);
baidudoc.getelementbyid("kw").setattribute("value", testvalue);
baidudoc.getelementbyid("su").click();
}
}
}
最后运行一下程序可以看到我们可以正常获取到百度上的元素了。
补充一下路过秋天说的问题:
其实关于这些接口其实我也没有很深入的研究过,不过网上倒是能搜到很多相关资料介绍这些接口的不同,我这里给一个链接:
http://hi.baidu.com/christole/item/1c8dfd1a791a53643f87ced8
然后关于我上面的代码为什么要使用ihmldocument3,而不是其它两个接口,因为ihmldocument3这个接口里面定义了我需要的getelementbyid这个方法。
通过查看msdn,你可以找到你需要的属性或者方法,然后直接在代码里面转换为你需要的类型使用就可以了,它们之间都是可以互相转化的。比如上面我用完了getelementbyid方法,我需要查看网页的title,那么可以将我上面的baidudoc变量强制转为ihmldocument2,然后就可以直接使用它的title属性了。
参考链接:
http://msdn.microsoft.com/en-us/library/aa752052(v=vs.85).aspx
http://codecentrix.blogspot.com/2007/10/when-ihtmlwindow2getdocument-returns.html
http://msdn.microsoft.com/en-us/library/aa752641(v=vs.85).aspx