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就能解决问题了。
解决方案:直接上代码吧
1 /* ------------------------------------------------------------------------- 2 // FileName : calljs_helper.h 3 // Creator : linyehui 4 // Date : 2013/11/16 01:18:09 5 // Brief : 调用WebBrowser控件内嵌页上的JS函数,iframe中的也能调到 6 // 7 // $Id: $ 8 // -----------------------------------------------------------------------*/ 9 #ifndef __CALLJS_HELPER_H__ 10 #define __CALLJS_HELPER_H__ 11 12 // ------------------------------------------------------------------------- 13 namespace calljs_helper 14 { 15 bool CallFunction( 16 CComPtr<IWebBrowser2> spIWebBrowser, 17 LPCTSTR lpFuncName, 18 const vector<wstring>& paramArray, 19 CComVariant * pVarResult = NULL, 20 bool bEnumFrame = true); 21 22 } // namespace 23 24 // ------------------------------------------------------------------------- 25 // $Log: $ 26 27 #endif /* __CALLJS_HELPER_H__ */
1 /* ------------------------------------------------------------------------- 2 // FileName : calljs_helper.cpp 3 // Creator : linyehui 4 // Date : 2013/11/16 01:18:14 5 // Brief : 调用WebBrowser控件内嵌页上的JS函数,iframe中的也能调到 6 // 7 // $Id: $ 8 // -----------------------------------------------------------------------*/ 9 10 #include "stdafx.h" 11 #include "calljs_helper.h" 12 13 // ------------------------------------------------------------------------- 14 15 CComPtr<IWebBrowser2> HtmlWindowToHtmlWebBrowser(CComPtr<IHTMLWindow2> spWindow) 16 { 17 ATLASSERT(spWindow != NULL); 18 CComQIPtr<IServiceProvider> spServiceProvider = spWindow; 19 if (spServiceProvider == NULL) 20 { 21 return CComPtr<IWebBrowser2>(); 22 } 23 24 CComPtr<IWebBrowser2> spWebBrws; 25 HRESULT hRes = spServiceProvider->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, (void**)&spWebBrws); 26 if (hRes != S_OK) 27 { 28 return CComPtr<IWebBrowser2>(); 29 } 30 31 return spWebBrws; 32 } 33 34 // Converts a IHTMLWindow2 object to a IHTMLDocument2. Returns NULL in case of failure. 35 // It takes into account accessing the DOM across frames loaded from different domains. 36 CComPtr<IHTMLDocument2> HtmlWindowToHtmlDocument(CComPtr<IHTMLWindow2> spWindow) 37 { 38 ATLASSERT(spWindow != NULL); 39 CComPtr<IHTMLDocument2> spDocument; 40 HRESULT hRes = spWindow->get_document(&spDocument); 41 if ((S_OK == hRes) && (spDocument != NULL)) 42 { 43 // The html document was properly retrieved. 44 return spDocument; 45 } 46 47 // hRes could be E_ACCESSDENIED that means a security restriction that 48 // prevents scripting across frames that loads documents from different internet domains. 49 CComPtr<IWebBrowser2> spBrws = HtmlWindowToHtmlWebBrowser(spWindow); 50 if (spBrws == NULL) 51 { 52 return CComPtr<IHTMLDocument2>(); 53 } 54 55 // Get the document object from the IWebBrowser2 object. 56 CComPtr<IDispatch> spDisp; 57 hRes = spBrws->get_Document(&spDisp); 58 spDocument = spDisp; 59 return spDocument; 60 } 61 62 bool CallFunctionInDocument( 63 CComPtr<IHTMLDocument2> spDocument2, 64 LPCTSTR lpFuncName, 65 const vector<wstring>& paramArray, 66 CComVariant * pVarResult) 67 { 68 if (!spDocument2) 69 return false; 70 71 CComPtr<IDispatch> spScript; 72 if (FAILED(spDocument2->get_Script(&spScript))) { return false; } 73 74 CComBSTR bstrMember(lpFuncName); 75 DISPID dispid = NULL; 76 HRESULT hr = spScript->GetIDsOfNames(IID_NULL, &bstrMember, 1, LOCALE_SYSTEM_DEFAULT, &dispid); 77 if (FAILED(hr)) { return false; } 78 79 const int arraySize = paramArray.size(); 80 81 DISPPARAMS dispparams; 82 memset(&dispparams, 0, sizeof dispparams); 83 dispparams.cArgs = arraySize; 84 dispparams.rgvarg = new VARIANT[dispparams.cArgs]; 85 86 for (int i = 0; i < arraySize; i++) 87 { 88 CComBSTR bstr = paramArray[arraySize - 1 - i].c_str(); // back reading 89 bstr.CopyTo(&dispparams.rgvarg[i].bstrVal); 90 dispparams.rgvarg[i].vt = VT_BSTR; 91 } 92 dispparams.cNamedArgs = 0; 93 94 EXCEPINFO excepInfo; 95 memset(&excepInfo, 0, sizeof excepInfo); 96 CComVariant vaResult; 97 UINT nArgErr = (UINT)-1; // initialize to invalid arg 98 99 hr = spScript->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, &dispparams, &vaResult, &excepInfo, &nArgErr); 100 101 delete [] dispparams.rgvarg; 102 if (FAILED(hr)) { return false; } 103 104 if (pVarResult) { *pVarResult = vaResult; } 105 106 return true; 107 } 108 109 void EnumFrame( 110 CComPtr<IHTMLDocument2> spIHTMLDocument2, 111 LPCTSTR lpFuncName,const vector<wstring>& paramArray, 112 CComVariant* pVarResult) 113 { 114 if ( !spIHTMLDocument2 ) 115 return; 116 117 CComPtr< IHTMLFramesCollection2 > spFramesCollection2; 118 spIHTMLDocument2->get_frames( &spFramesCollection2 ); 119 120 long nFrameCount=0; 121 HRESULT hr = spFramesCollection2->get_length( &nFrameCount ); 122 if ( FAILED ( hr ) || 0 == nFrameCount ) 123 return; 124 125 for(long i = 0; i < nFrameCount; i++) 126 { 127 CComVariant vDispWin2; 128 hr = spFramesCollection2->item( &CComVariant(i), &vDispWin2 ); 129 if ( FAILED ( hr ) ) 130 continue; 131 132 CComQIPtr< IHTMLWindow2 > spWin2 = vDispWin2.pdispVal; 133 if( !spWin2 ) 134 continue; 135 136 CComPtr < IHTMLDocument2 > spDoc2; 137 spDoc2 = HtmlWindowToHtmlDocument(spWin2); 138 if (!spDoc2) 139 continue; 140 141 CallFunctionInDocument(spDoc2, lpFuncName, paramArray, pVarResult); 142 } 143 } 144 145 bool calljs_helper::CallFunction( 146 CComPtr<IWebBrowser2> spIWebBrowser, 147 LPCTSTR lpFuncName, 148 const vector<wstring>& paramArray, 149 CComVariant * pVarResult, 150 bool bEnumFrame) 151 { 152 if (!spIWebBrowser) 153 return false; 154 155 CComPtr<IDispatch> spDispDoc; 156 HRESULT hr = spIWebBrowser->get_Document(&spDispDoc); 157 if (FAILED(hr)) 158 return false; 159 160 CComQIPtr<IHTMLDocument2> spDocument2 = spDispDoc; 161 if (!spDocument2) 162 return false; 163 164 CallFunctionInDocument(spDocument2, lpFuncName, paramArray, pVarResult); 165 166 if (bEnumFrame) 167 { 168 EnumFrame(spDocument2, lpFuncName, paramArray, pVarResult); 169 } 170 171 return true; 172 } 173 174 // ------------------------------------------------------------------------- 175 // $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.