WebBrowser内嵌页面的跨域调用问题
很早之前我写过一篇Blog:网页通过External接口与WebBrowser交互,文中的交互其实只介绍了JS调用C++的部分,而C++调用JS由于微软自己的例子太多,那篇文章就没介绍,不过我最近遇到了一个新问题,和C++调用JS有关,所以重新梳理了下这块的逻辑,把之前的代码完善了下。
我遇到的问题:
内嵌IE浏览器控件WebBrowser的内嵌页host.html中使用iframe又嵌套了一个页面iframe.html,iframe.html上有个JS方法,我用C++调用不到,而host.html上的JS方法可以正常调用到。
问题分析:
从JS来说,这是个跨域问题,host.html和iframe.html不在一个域内;
JS解决跨域问题的方案其实也是有的,但这个不是我们本文的重点,本文的重点是怎么通过WebBrowser控件直接来解决这个“跨域调用”的问题。
问题的根本原因在于:
调用网页的JS需要拿到IHTMLDocument2接口,而每个iframe都有自己对应的IHTMLDocument2,所以我们只要能拿到iframe对应的IHTMLDocument2就能解决问题了。
解决方案:直接上代码吧
/* -------------------------------------------------------------------------
// FileName : calljs_helper.h
// Creator : linyehui
// Date : 2013/11/16 01:18:09
// Brief : 调用WebBrowser控件内嵌页上的JS函数,iframe中的也能调到
//
// $Id: $
// -----------------------------------------------------------------------*/
#ifndef __CALLJS_HELPER_H__
#define __CALLJS_HELPER_H__ // -------------------------------------------------------------------------
namespace calljs_helper
{
bool CallFunction(
CComPtr<IWebBrowser2> spIWebBrowser,
LPCTSTR lpFuncName,
const vector<wstring>& paramArray,
CComVariant * pVarResult = NULL,
bool bEnumFrame = true); } // namespace // -------------------------------------------------------------------------
// $Log: $ #endif /* __CALLJS_HELPER_H__ */
/* -------------------------------------------------------------------------
// FileName : calljs_helper.cpp
// Creator : linyehui
// Date : 2013/11/16 01:18:14
// Brief : 调用WebBrowser控件内嵌页上的JS函数,iframe中的也能调到
//
// $Id: $
// -----------------------------------------------------------------------*/ #include "stdafx.h"
#include "calljs_helper.h" // ------------------------------------------------------------------------- CComPtr<IWebBrowser2> HtmlWindowToHtmlWebBrowser(CComPtr<IHTMLWindow2> spWindow)
{
ATLASSERT(spWindow != NULL);
CComQIPtr<IServiceProvider> spServiceProvider = spWindow;
if (spServiceProvider == NULL)
{
return CComPtr<IWebBrowser2>();
} CComPtr<IWebBrowser2> spWebBrws;
HRESULT hRes = spServiceProvider->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, (void**)&spWebBrws);
if (hRes != S_OK)
{
return CComPtr<IWebBrowser2>();
} return spWebBrws;
} // Converts a IHTMLWindow2 object to a IHTMLDocument2. Returns NULL in case of failure.
// It takes into account accessing the DOM across frames loaded from different domains.
CComPtr<IHTMLDocument2> HtmlWindowToHtmlDocument(CComPtr<IHTMLWindow2> spWindow)
{
ATLASSERT(spWindow != NULL);
CComPtr<IHTMLDocument2> spDocument;
HRESULT hRes = spWindow->get_document(&spDocument);
if ((S_OK == hRes) && (spDocument != NULL))
{
// The html document was properly retrieved.
return spDocument;
} // hRes could be E_ACCESSDENIED that means a security restriction that
// prevents scripting across frames that loads documents from different internet domains.
CComPtr<IWebBrowser2> spBrws = HtmlWindowToHtmlWebBrowser(spWindow);
if (spBrws == NULL)
{
return CComPtr<IHTMLDocument2>();
} // Get the document object from the IWebBrowser2 object.
CComPtr<IDispatch> spDisp;
hRes = spBrws->get_Document(&spDisp);
spDocument = spDisp;
return spDocument;
} bool CallFunctionInDocument(
CComPtr<IHTMLDocument2> spDocument2,
LPCTSTR lpFuncName,
const vector<wstring>& paramArray,
CComVariant * pVarResult)
{
if (!spDocument2)
return false; CComPtr<IDispatch> spScript;
if (FAILED(spDocument2->get_Script(&spScript))) { return false; } CComBSTR bstrMember(lpFuncName);
DISPID dispid = NULL;
HRESULT hr = spScript->GetIDsOfNames(IID_NULL, &bstrMember, , LOCALE_SYSTEM_DEFAULT, &dispid);
if (FAILED(hr)) { return false; } const int arraySize = paramArray.size(); DISPPARAMS dispparams;
memset(&dispparams, , sizeof dispparams);
dispparams.cArgs = arraySize;
dispparams.rgvarg = new VARIANT[dispparams.cArgs]; for (int i = ; i < arraySize; i++)
{
CComBSTR bstr = paramArray[arraySize - - i].c_str(); // back reading
bstr.CopyTo(&dispparams.rgvarg[i].bstrVal);
dispparams.rgvarg[i].vt = VT_BSTR;
}
dispparams.cNamedArgs = ; EXCEPINFO excepInfo;
memset(&excepInfo, , sizeof excepInfo);
CComVariant vaResult;
UINT nArgErr = (UINT)-; // initialize to invalid arg hr = spScript->Invoke(dispid, IID_NULL, , DISPATCH_METHOD, &dispparams, &vaResult, &excepInfo, &nArgErr); delete [] dispparams.rgvarg;
if (FAILED(hr)) { return false; } if (pVarResult) { *pVarResult = vaResult; } return true;
} void EnumFrame(
CComPtr<IHTMLDocument2> spIHTMLDocument2,
LPCTSTR lpFuncName,const vector<wstring>& paramArray,
CComVariant* pVarResult)
{
if ( !spIHTMLDocument2 )
return; CComPtr< IHTMLFramesCollection2 > spFramesCollection2;
spIHTMLDocument2->get_frames( &spFramesCollection2 ); long nFrameCount=;
HRESULT hr = spFramesCollection2->get_length( &nFrameCount );
if ( FAILED ( hr ) || == nFrameCount )
return; for(long i = ; i < nFrameCount; i++)
{
CComVariant vDispWin2;
hr = spFramesCollection2->item( &CComVariant(i), &vDispWin2 );
if ( FAILED ( hr ) )
continue; CComQIPtr< IHTMLWindow2 > spWin2 = vDispWin2.pdispVal;
if( !spWin2 )
continue; CComPtr < IHTMLDocument2 > spDoc2;
spDoc2 = HtmlWindowToHtmlDocument(spWin2);
if (!spDoc2)
continue; CallFunctionInDocument(spDoc2, lpFuncName, paramArray, pVarResult);
}
} bool calljs_helper::CallFunction(
CComPtr<IWebBrowser2> spIWebBrowser,
LPCTSTR lpFuncName,
const vector<wstring>& paramArray,
CComVariant * pVarResult,
bool bEnumFrame)
{
if (!spIWebBrowser)
return false; CComPtr<IDispatch> spDispDoc;
HRESULT hr = spIWebBrowser->get_Document(&spDispDoc);
if (FAILED(hr))
return false; CComQIPtr<IHTMLDocument2> spDocument2 = spDispDoc;
if (!spDocument2)
return false; CallFunctionInDocument(spDocument2, lpFuncName, paramArray, pVarResult); if (bEnumFrame)
{
EnumFrame(spDocument2, lpFuncName, paramArray, pVarResult);
} return true;
} // -------------------------------------------------------------------------
// $Log: $
完整的例子代码在:这里下载
另外再附上帮助我解决问题的参考资料:
[http://blog.csdn.net/skyremember/article/details/3422841 IHTMLWindow2的get_document方法有时候会返回E_ACCESSDENIED]
[http://blog.csdn.net/nvidiacuda/article/details/9300869 VC++实现浏览器自动填表]
[http://www.itdelphi.com/delphibbs/doc/2007/3845499.htm webbrowser控件的IHTMLDocument3 or2接口的iframe问题(非安全设置)]
[http://www.cnblogs.com/rainman/archive/2011/02/21/1960044.html window.name实现的跨域数据传输]
[http://www.cnblogs.com/rainman/archive/2011/02/20/1959325.html JavaScript跨域总结与解决办法]
The End.
WebBrowser内嵌页面的跨域调用问题的更多相关文章
- AJAX跨域调用相关知识-CORS和JSONP(引)
AJAX跨域调用相关知识-CORS和JSONP 1.什么是跨域 跨域问题产生的原因,是由于浏览器的安全机制,JS只能访问与所在页面同一个域(相同协议.域名.端口)的内容. 但是我们项目开发过程中,经常 ...
- 子页面iframe跨域执行父页面定义的JS方法
问题需求:父页面与子页面iframe跨域嵌套,子页面要触发父页面所定义的js方法.父子页面的数据传递. 下文中会用到一些文件:父页面: parent.html嵌在父页面的子iframe页面:child ...
- AJAX跨域调用ASP.NET MVC或者WebAPI服务的解决方案
问题描述 当跨域(cross domain)调用ASP.NET MVC或者ASP.NET Web API编写的服务时,会发生无法访问的情况. 重现方式 使用模板创建一个最简单的ASP.NET Web ...
- 使用jsonp跨域调用百度js实现搜索框智能提示,并实现鼠标和键盘对弹出框里候选词的操作【附源码】
项目中常常用到搜索,特别是导航类的网站.自己做关键字搜索不太现实,直接调用百度的是最好的选择.使用jquery.ajax的jsonp方法可以异域调用到百度的js并拿到返回值,当然$.getScript ...
- 跨域调用webapi
web端跨域调用webapi 在做Web开发中,常常会遇到跨域的问题,到目前为止,已经有非常多的跨域解决方案. 通过自己的研究以及在网上看了一些大神的博客,写了一个Demo 首先新建一个webap ...
- 关于AJAX跨域调用ASP.NET MVC或者WebAPI服务的问题及解决方案
作者:陈希章 时间:2014-7-3 问题描述 当跨域(cross domain)调用ASP.NET MVC或者ASP.NET Web API编写的服务时,会发生无法访问的情况. 重现方式 使用模 ...
- 跨域调用webapi web端跨域调用webapi
web端跨域调用webapi 在做Web开发中,常常会遇到跨域的问题,到目前为止,已经有非常多的跨域解决方案. 通过自己的研究以及在网上看了一些大神的博客,写了一个Demo 首先新建一个webap ...
- jquery Ajax跨域调用WebServices方法
由于公司需要开发一个手机页面,想提供给同事直接在手机上可以查询SAP资料.数据需要使用js调用webserver来获取. 因为初次使用Jquery调用Webserver,所以期间并不顺利.测试调用We ...
- web端跨域调用webapi
在做Web开发中,常常会遇到跨域的问题,到目前为止,已经有非常多的跨域解决方案. 通过自己的研究以及在网上看了一些大神的博客,写了一个Demo 首先新建一个webapi的程序,如下图所示: 由于微软已 ...
随机推荐
- jquery+css 实现即时变化颜色主题(通过input输入颜色值进行改变)
实现效果需要自行导入jquery.js <!DOCTYPE html> <html lang="en"> <head> <meta cha ...
- 用DD-WRT自建计费WiFi热点
架设无线网络(Wlan)向周围的用户有偿共享网络. 传统的方法,能够使用专业的无线与宽带计费网关设备和软件.比方MikroTik等. 只是,对于个人架设WiFi热点来说.这些方案太过昂贵,安装设置也很 ...
- 【每日Scrum】第三天(4.13) TD学生助手Sprint1站立会议
TD学生助手Sprint1站立会议(4.13) 任务看板 站立会议内容 组员 昨天 今天 困难 签到 刘铸辉 (组长) 昨天完成了课程的增删改查功能 今天早晨静姐调整了下界面和配色,下午和宝月兄一起做 ...
- Terminal emulator
http://en.wikipedia.org/wiki/Terminal_emulator Terminal emulator From Wikipedia, the free encycloped ...
- mongoDB之监控工具mongotop
mongotop也是mongodb-win32-x86_64-2.2.1\bin下的一个内置工具,mongotop提供了一个方法,用来跟踪一个MongoDB的实例,查看哪些大量的时间花费在读取和写入数 ...
- AAC包增加ADTS头Without MediaCodec
AAC原始码流无法直接播放,一般需要封装为ADTS格式才能再次使用,本博主在android中用MediaCodec编码得到的AAC就是raw格式,为了保存为.aac格式,需要增加adts头,这样就可以 ...
- 开源流媒体播放器EasyPlayer
配套开源流媒体服务器EasyDarwin,我们开发了一款开源的流媒体播放器EasyPlayer(现在已经升级合并到开源EasyClient中):同样,EasyPlayer目前只支持RTSP流媒体协议, ...
- 远程访问Linux的常见方式
访问方式 不加密文字界面:telnet.rsh 加密文字界面:SSH 图形界面:Xdmcp.RDP.VNC SSH SSH用来取代finger.R shell(rlogin.rcp.rsh).teln ...
- Eclipse中连接Sql Sever2008 -----转自Yogurshine
Eclipse中连接Sql Sever2008 -----转自Yogurshine 一 SQl Sever服务器配置 1我之前已经安装好SQL Sever 2008R2.(注意:安装一遍未成功时,一定 ...
- h5 placeholder 设置无效
下面设置方式无效: ::-webkit-input-placeholder { /* WebKit browsers */ color: #999; } :-moz-placeholder { /* ...