当WebBrowser控件(CDHtmlDialog自动创建了WebBrowser控件)加载的网页中含有错误Javascript代码时默认情况下控件会弹出错误信息提示对话框,相对于用户体验来说这样的提示完全不是开发人员想要的,针对这个问题有两个解决方案,一是完全屏蔽掉错误提示,二是控制错误的提示并且记录错误信息同时也可以控制出现错误后Javascript是否继续执行。
1、屏蔽错误信息提示
1 | m_pBrowserApp->put_Silent(VARIANT_TRUE); |
在CDHtmlDialog::OnInitDialog()的代码中首先了创建WebBrowser控件,然后把控件的Browser对象赋值给m_pBrowserApp(这是CDHtmlDialog完成的不需要自己处理)。WebBrowser的put_Silent函数在官方给出的说明是禁用所有的对话框,但例外情况是它不会影响SSL安全认证需要的进示对话框。绝大多数情况下这就可以解决问题了,记得很久以前我遇到过一种情况就是虽然调用了put_Silent但是还是有极个别的js错误是无法屏蔽掉的依然会显示出来(在网页含有嵌套页面时会错误无法屏蔽,不知道是否还有其它情况),现在找不到这样的网页了,如果谁遇到这种情况了建议给我发上个URL让我也重温一下当年阳光灿烂的时刻。
2、控制错误提示并进行记录
这要比第一种方法复杂上许多,简短的来说就是自定义COleControlSite类并实现IOleCommandTarget接口,IOleCommandTarget接口是错误控制的关健,错误发生时会触发此接口的Exec函数并为nCmdID参数赋值为OLECMDID_SHOWSCRIPTERROR,这样就可以得到错误信息了。
01 | IOleCommandTarget : public IUnknown |
04 |
virtual HRESULT STDMETHODCALLTYPE QueryStatus( |
05 | __RPC__in_opt const GUID *pguidCmdGroup, |
07 | __RPC__inout_ecount_full(cCmds) OLECMD prgCmds[ ], |
08 | __RPC__inout_opt OLECMDTEXT *pCmdText) = 0; |
10 |
virtual HRESULT STDMETHODCALLTYPE Exec( |
11 | __RPC__in_opt const GUID *pguidCmdGroup, |
14 | __RPC__in_opt VARIANT *pvaIn, |
15 | __RPC__inout_opt VARIANT *pvaOut) = 0; |
现在我们开始实现自定义的COleControlSite
01 | class CMyControlSite : public COleControlSite |
05 |
CMyControlSite(COleControlContainer *pCntr):COleControlSite(pCntr) {} |
09 |
DECLARE_INTERFACE_MAP() |
10 |
BEGIN_INTERFACE_PART(OleCommandTarget, IOleCommandTarget) |
11 |
STDMETHOD(QueryStatus)( const GUID *pguidCmdGroup, ULONG cCmds, OLECMD prgCmds[], OLECMDTEXT *pCmdText); |
12 |
STDMETHOD(Exec)( const GUID* pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG* pvaIn, VARIANTARG* pvaOut); |
13 |
END_INTERFACE_PART(OleCommandTarget) |
MFC提供了很多宏用于简化COM相关功能的开发,对COM接口的实现方式在MFC中具体体现方式是内嵌类,背后的设计思想是COM聚合,每个接口都产生一个内嵌类,所有的接口都聚合到外层的类。DECLARE_INTERFACE_MAP() 实际上就是定义一个数组以及查询操作,BEGIN_INTERFACE_PART定义一个命名为XOleCommandTarget的内嵌类(内嵌类的命名规则是XName)并定义IUnknown接口的三个方法AddRef、Release、QueryInterface。END_INTERFACE_PART定义一个m_xOleCommandTarget的成员类型为XOleCommandTarget(定义的成员命名规则就是m_xName),并把XOleCommandTarget类声明为外层类的友元类在示例中外层类指CMyControlSite。
STDMETHOD宏定义为virtual __declspec(nothrow) HRESULT __stdcall,该宏定义函数为虚函数返回值为HRESULT,函数调用约定为__stdcall,并且在此函数上禁止异常。__declspec(nothrow)用定告诉编译器它修饰的函数以及此函数调用的函数不会产生C++异常调用从可以优化代码性能和代码尺寸(默认情况下C++编译器为了进行异常处理会在拥有throw调用的函数中自动生成相关的异常处理代码)。通常情况下HRESULT返回值就表达了错误信息,HRESULT是个32位值,不同的位域用于不同的目的,也可以使用自定义的位域,具体的信息可以参考http://en.wikipedia.org/wiki/HRESULT。由于COM本身的语言中立性所以不应该在COM组件对外公布的信息中掺杂特定语言相关的特性。如果需要提供更详尽的错误信息那么应该实现COM的IErrorInfo接口。言归正传以下是CMyControlSite的类实现代码
01 | BEGIN_INTERFACE_MAP(CMyControlSite, COleControlSite) |
02 |
INTERFACE_PART(CMyControlSite, IID_IOleCommandTarget, OleCommandTarget) |
06 | HRESULT CMyControlSite::XOleCommandTarget::Exec |
07 | ( const GUID* pguidCmdGroup, DWORD nCmdID, |
08 |
DWORD nCmdexecopt, VARIANTARG* pvaIn, VARIANTARG* pvaOut ) |
10 |
HRESULT hr = OLECMDERR_E_NOTSUPPORTED; |
12 |
if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CGID_DocHostCommandHandler)) |
18 |
case OLECMDID_SHOWSCRIPTERROR: |
20 |
IHTMLDocument2* pDoc = NULL; |
21 |
IHTMLWindow2* pWindow = NULL; |
22 |
IHTMLEventObj* pEventObj = NULL; |
25 |
SysAllocString(L "errLine" ), |
26 |
SysAllocString(L "errCharacter" ), |
27 |
SysAllocString(L "errCode" ), |
28 |
SysAllocString(L "errMsg" ), |
29 |
SysAllocString(L "errUrl" ) |
32 |
VARIANT rgvaEventInfo[5]; |
34 |
BOOL fContinueRunningScripts = true ; |
39 |
hr = pvaIn->punkVal->QueryInterface(IID_IHTMLDocument2, ( void **) &pDoc); |
41 |
hr = pDoc->get_parentWindow(&pWindow); |
44 |
hr = pWindow->get_event(&pEventObj); |
46 |
for ( int i = 0; i < 5; i++) |
49 |
hr = pEventObj->GetIDsOfNames(IID_NULL, &rgwszNames[i], 1, |
50 |
LOCALE_SYSTEM_DEFAULT, &rgDispIDs[i]); |
52 |
hr = pEventObj->Invoke(rgDispIDs[i], IID_NULL, |
54 |
DISPATCH_PROPERTYGET, ¶ms, &rgvaEventInfo[i], |
02 |
SysFreeString(rgwszNames[i]); |
09 |
(*pvaOut).vt = VT_BOOL; |
10 |
if (fContinueRunningScripts) |
13 |
(*pvaOut).boolVal = VARIANT_TRUE; |
18 |
(*pvaOut).boolVal = VARIANT_FALSE; |
23 |
hr =OLECMDERR_E_NOTSUPPORTED; |
29 |
hr = OLECMDERR_E_UNKNOWNGROUP; |
35 | ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::AddRef() |
37 |
METHOD_PROLOGUE(CMyControlSite, OleCommandTarget) |
38 |
return pThis->ExternalAddRef(); |
42 | ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::Release() |
44 |
METHOD_PROLOGUE(CMyControlSite, OleCommandTarget) |
45 |
return pThis->ExternalRelease(); |
48 | HRESULT FAR EXPORT CMyControlSite::XOleCommandTarget::QueryInterface(REFIID riid, void **ppvObj) |
50 |
METHOD_PROLOGUE(CMyControlSite, OleCommandTarget) |
51 |
HRESULT hr = ( HRESULT )pThis->ExternalQueryInterface(&riid, ppvObj); |
55 | STDMETHODIMP CMyControlSite::XOleCommandTarget::QueryStatus( |
56 | const GUID __RPC_FAR *pguidCmdGroup, |
58 | OLECMD __RPC_FAR prgCmds[ ], |
59 | OLECMDTEXT __RPC_FAR *pCmdText |
62 |
METHOD_PROLOGUE(CMyControlSite, OleCommandTarget) |
63 |
return OLECMDERR_E_NOTSUPPORTED; |
实现CMyControlSite后需要应用到CDHtmlDialog上,重写CreateControlSite虚函数既可
01 | virtual BOOL CreateControlSite(COleControlContainer* pContainer, |
02 |
COleControlSite** ppSite, UINT nID , REFCLSID clsid ) |
10 |
CMyControlSite *pBrowserSite = |
11 |
new CMyControlSite (pContainer, this ); |
15 |
*ppSite = pBrowserSite; |
现在就可以去编译测试了。到目前还有一个问题没有考虑,如果这段代码整合到现有的CDHtmlDialog应用中而现有的代码使用了其它默认的设定比如说自定义WebBrowser的右健菜单或使用了GetIDispatch函数等情况下原有的代码就不能正常工作了。这部分功能是在MFC的CBrowserControlSite类中处理的,所以CMyControlSite应该把CBrowserControlSite的功能也实现一遍以使CDHtmlDialog的原有封装性不被破坏。在CMyControlSite中实现IDocHostUIHandler接口既可完成此功能。代码声明如下
1 | CMyControlSite(COleControlContainer *pCnt, CDHtmlDialog *pHandler):COleControlSite(pCnt),m_pHandler(pHandler) {} |
1 | CDHtmlDialog *m_pHandler;<br> |
01 | BEGIN_INTERFACE_PART(DocHostUIHandler, IDocHostUIHandler) |
02 |
STDMETHOD(ShowContextMenu)( DWORD , LPPOINT, LPUNKNOWN, LPDISPATCH); |
03 |
STDMETHOD(GetHostInfo)(DOCHOSTUIINFO*); |
04 |
STDMETHOD(ShowUI)( DWORD , LPOLEINPLACEACTIVEOBJECT, |
05 |
LPOLECOMMANDTARGET, LPOLEINPLACEFRAME, LPOLEINPLACEUIWINDOW); |
06 |
STDMETHOD(HideUI)( void ); |
07 |
STDMETHOD(UpdateUI)( void ); |
08 |
STDMETHOD(EnableModeless)( BOOL ); |
09 |
STDMETHOD(OnDocWindowActivate)( BOOL ); |
10 |
STDMETHOD(OnFrameWindowActivate)( BOOL ); |
11 |
STDMETHOD(ResizeBorder)(LPCRECT, LPOLEINPLACEUIWINDOW, BOOL ); |
12 |
STDMETHOD(TranslateAccelerator)(LPMSG, const GUID*, DWORD ); |
13 |
STDMETHOD(GetOptionKeyPath)(OLECHAR **, DWORD ); |
14 |
STDMETHOD(GetDropTarget)(LPDROPTARGET, LPDROPTARGET*); |
15 |
STDMETHOD(GetExternal)(LPDISPATCH*); |
16 |
STDMETHOD(TranslateUrl)( DWORD , OLECHAR*, OLECHAR **); |
17 |
STDMETHOD(FilterDataObject)(LPDATAOBJECT , LPDATAOBJECT*); |
18 |
END_INTERFACE_PART(DocHostUIHandler) |
以下是实现代码
001 | BEGIN_INTERFACE_MAP(CMyControlSite, COleControlSite) |
002 |
INTERFACE_PART(CMyControlSite, IID_IDocHostUIHandler, DocHostUIHandler) |
003 |
INTERFACE_PART(CMyControlSite, IID_IOleCommandTarget, OleCommandTarget) |
006 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetExternal(LPDISPATCH *lppDispatch) |
008 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
009 |
return pThis->m_pHandler->GetExternal(lppDispatch); |
014 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::ShowContextMenu( |
015 |
DWORD dwID, LPPOINT ppt, LPUNKNOWN pcmdTarget, LPDISPATCH pdispReserved) |
017 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
018 |
return pThis->m_pHandler->ShowContextMenu(dwID, ppt, pcmdTarget, pdispReserved); |
021 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetHostInfo( |
024 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
025 |
return pThis->m_pHandler->GetHostInfo(pInfo); |
029 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::ShowUI( |
030 |
DWORD dwID, LPOLEINPLACEACTIVEOBJECT pActiveObject, |
031 |
LPOLECOMMANDTARGET pCommandTarget, LPOLEINPLACEFRAME pFrame, |
032 |
LPOLEINPLACEUIWINDOW pDoc) |
034 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
035 |
return pThis->m_pHandler->ShowUI(dwID, pActiveObject, pCommandTarget, pFrame, pDoc); |
038 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::HideUI( void ) |
040 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
041 |
return pThis->m_pHandler->HideUI(); |
044 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::UpdateUI( void ) |
046 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
047 |
return pThis->m_pHandler->UpdateUI(); |
051 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::EnableModeless( BOOL fEnable) |
053 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
054 |
return pThis->m_pHandler->EnableModeless(fEnable); |
057 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::OnDocWindowActivate( BOOL fActivate) |
059 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
060 |
return pThis->m_pHandler->OnDocWindowActivate(fActivate); |
063 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::OnFrameWindowActivate( |
066 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
067 |
return pThis->m_pHandler->OnFrameWindowActivate(fActivate); |
070 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::ResizeBorder( |
071 |
LPCRECT prcBorder, LPOLEINPLACEUIWINDOW pUIWindow, BOOL fFrameWindow) |
073 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
074 |
return pThis->m_pHandler->ResizeBorder(prcBorder, pUIWindow, fFrameWindow); |
077 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::TranslateAccelerator( |
078 |
LPMSG lpMsg, const GUID* pguidCmdGroup, DWORD nCmdID) |
080 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
081 |
return pThis->m_pHandler->TranslateAccelerator(lpMsg, pguidCmdGroup, nCmdID); |
085 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetOptionKeyPath( |
086 |
LPOLESTR* pchKey, DWORD dwReserved) |
088 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
089 |
return pThis->m_pHandler->GetOptionKeyPath(pchKey, dwReserved); |
093 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetDropTarget( |
094 |
LPDROPTARGET pDropTarget, LPDROPTARGET* ppDropTarget) |
096 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
097 |
return pThis->m_pHandler->GetDropTarget(pDropTarget, ppDropTarget); |
100 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::TranslateUrl( |
101 |
DWORD dwTranslate, OLECHAR* pchURLIn, OLECHAR** ppchURLOut) |
103 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
104 |
return pThis->m_pHandler->TranslateUrl(dwTranslate, pchURLIn, ppchURLOut); |
107 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::FilterDataObject( |
108 |
LPDATAOBJECT pDataObject, LPDATAOBJECT* ppDataObject) |
110 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
111 |
return pThis->m_pHandler->FilterDataObject(pDataObject, ppDataObject); |
115 | STDMETHODIMP_( ULONG ) CMyControlSite::XDocHostUIHandler::AddRef() |
117 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
118 |
return pThis->ExternalAddRef(); |
121 | STDMETHODIMP_( ULONG ) CMyControlSite::XDocHostUIHandler::Release() |
123 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
124 |
return pThis->ExternalRelease(); |
127 | STDMETHODIMP CMyControlSite::XDocHostUIHandler::QueryInterface( |
128 |
REFIID iid, LPVOID far* ppvObj) |
130 |
METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) |
131 |
return pThis->ExternalQueryInterface(&iid, ppvObj); |
STDMETHODIMP宏的定义是HRESULT __stdcall,STDMETHODIMP_宏指定了返回值,这两个宏用在cpp实现文件中,对应用于声明时使用的STDMETHOD和STDMETHOD_。
METHOD_PROLOGUE_EX_宏定义了pThis指针来指向外层类。
以上代码基于VS2008,由于不同版本的VS所带的MFC库版本不尽一致所以需要根本具体的版本来处理,目前已知的不同部分主要集中在CreateControlSite上。