当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,这样就可以得到错误信息了。
1 IOleCommandTarget : public IUnknown 2 { 3 public: 4 virtual /* [input_sync] */ HRESULT STDMETHODCALLTYPE QueryStatus( 5 /* [unique][in] */ __RPC__in_opt const GUID *pguidCmdGroup, 6 /* [in] */ ULONG cCmds, 7 /* [out][in][size_is] */ __RPC__inout_ecount_full(cCmds) OLECMD prgCmds[ ], 8 /* [unique][out][in] */ __RPC__inout_opt OLECMDTEXT *pCmdText) = 0; 9 10 virtual HRESULT STDMETHODCALLTYPE Exec( 11 /* [unique][in] */ __RPC__in_opt const GUID *pguidCmdGroup, 12 /* [in] */ DWORD nCmdID, 13 /* [in] */ DWORD nCmdexecopt, 14 /* [unique][in] */ __RPC__in_opt VARIANT *pvaIn, 15 /* [unique][out][in] */ __RPC__inout_opt VARIANT *pvaOut) = 0; 16 17 };
现在我们开始实现自定义的COleControlSite
1 class CMyControlSite : public COleControlSite 2 { 3 4 public: 5 CMyControlSite(COleControlContainer *pCntr):COleControlSite(pCntr) {} 6 7 protected: 8 9 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的类实现代码
BEGIN_INTERFACE_MAP(CMyControlSite, COleControlSite) INTERFACE_PART(CMyControlSite, IID_IOleCommandTarget, OleCommandTarget)
1 <EM id=__mceDel><EM id=__mceDel>END_INTERFACE_MAP() 2 3 4 HRESULT CMyControlSite::XOleCommandTarget::Exec 5 (const GUID* pguidCmdGroup, DWORD nCmdID, 6 DWORD nCmdexecopt, VARIANTARG* pvaIn, VARIANTARG* pvaOut ) 7 { 8 HRESULT hr = OLECMDERR_E_NOTSUPPORTED; 9 //return S_OK; 10 if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CGID_DocHostCommandHandler)) 11 { 12 13 switch (nCmdID) 14 { 15 16 case OLECMDID_SHOWSCRIPTERROR: 17 { 18 IHTMLDocument2* pDoc = NULL; 19 IHTMLWindow2* pWindow = NULL; 20 IHTMLEventObj* pEventObj = NULL; 21 BSTR rgwszNames[5] = 22 { 23 <SPAN>SysAllocString(L"errorLine"), </SPAN><BR><SPAN>SysAllocString(L"errorCharacter"), </SPAN><BR><SPAN>SysAllocString(L"errorCode"), </SPAN><BR><SPAN>SysAllocString(L"errorMessage"), </SPAN><BR><SPAN>SysAllocString(L"errorUrl")</SPAN> 24 }; 25 DISPID rgDispIDs[5]; 26 VARIANT rgvaEventInfo[5]; 27 DISPPARAMS params; 28 BOOL fContinueRunningScripts = true; 29 30 params.cArgs = 0; 31 params.cNamedArgs = 0; 32 33 hr = pvaIn->punkVal->QueryInterface(IID_IHTMLDocument2, (void **) &pDoc); 34 35 hr = pDoc->get_parentWindow(&pWindow); 36 pDoc->Release(); 37 38 hr = pWindow->get_event(&pEventObj); 39 40 for (int i = 0; i < 5; i++) 41 { 42 43 hr = pEventObj->GetIDsOfNames(IID_NULL, &rgwszNames[i], 1, 44 LOCALE_SYSTEM_DEFAULT, &rgDispIDs[i]); 45 46 hr = pEventObj->Invoke(rgDispIDs[i], IID_NULL, 47 LOCALE_SYSTEM_DEFAULT, 48 DISPATCH_PROPERTYGET, ¶ms, &rgvaEventInfo[i], 49 NULL, NULL); 50 //可以在此记录错误信息</EM></EM> 51 52 53 //必须使用SysFreeString来释放SysAllocString分配的内存,SysAllocString在分配的内存中记录了字符的长度 54 SysFreeString(rgwszNames[i]); 55 } 56 57 // At this point, you would normally alert the user with 58 // the information about the error, which is now contained 59 // in rgvaEventInfo[]. Or, you could just exit silently. 60 61 (*pvaOut).vt = VT_BOOL; 62 if (fContinueRunningScripts) 63 { 64 // 在页面中继续执行脚本 65 (*pvaOut).boolVal = VARIANT_TRUE; 66 } 67 else 68 { 69 // 停止在页面中执行脚本 70 (*pvaOut).boolVal = VARIANT_FALSE; 71 } 72 break; 73 } 74 default: 75 hr =OLECMDERR_E_NOTSUPPORTED; 76 break; 77 } 78 } 79 else 80 { 81 hr = OLECMDERR_E_UNKNOWNGROUP; 82 } 83 return (hr); 84 } 85 86 87 ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::AddRef() 88 { 89 METHOD_PROLOGUE(CMyControlSite, OleCommandTarget) 90 return pThis->ExternalAddRef(); 91 } 92 93 94 ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::Release() 95 { 96 METHOD_PROLOGUE(CMyControlSite, OleCommandTarget) 97 return pThis->ExternalRelease(); 98 } 99 100 HRESULT FAR EXPORT CMyControlSite::XOleCommandTarget::QueryInterface(REFIID riid, void **ppvObj) 101 { 102 METHOD_PROLOGUE(CMyControlSite, OleCommandTarget) 103 HRESULT hr = (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObj); 104 return hr; 105 } 106 107 STDMETHODIMP CMyControlSite::XOleCommandTarget::QueryStatus( 108 /* [unique][in] */ const GUID __RPC_FAR *pguidCmdGroup, 109 /* [in] */ ULONG cCmds, 110 /* [out][in][size_is] */ OLECMD __RPC_FAR prgCmds[ ], 111 /* [unique][out][in] */ OLECMDTEXT __RPC_FAR *pCmdText 112 ) 113 { 114 METHOD_PROLOGUE(CMyControlSite, OleCommandTarget) 115 return OLECMDERR_E_NOTSUPPORTED; 116 }
实现CMyControlSite后需要应用到CDHtmlDialog上,重写CreateControlSite虚函数既可
1 virtual BOOL CreateControlSite(COleControlContainer* pContainer, 2 COleControlSite** ppSite, UINT nID , REFCLSID clsid ) 3 { 4 if(ppSite == NULL) 5 { 6 ASSERT(FALSE); 7 return FALSE; 8 } 9 10 CMyControlSite *pBrowserSite = 11 new CMyControlSite (pContainer, this); 12 if (!pBrowserSite) 13 return FALSE; 14 15 *ppSite = pBrowserSite; 16 return TRUE; 17 }
现在就可以去编译测试了。到目前还有一个问题没有考虑,如果这段代码整合到现有的CDHtmlDialog应用中而现有的代码使用了其它默认的设定比如说自定义WebBrowser的右健菜单或使用了GetIDispatch函数等情况下原有的代码就不能正常工作了。这部分功能是在MFC的CBrowserControlSite类中处理的,所以CMyControlSite应该把CBrowserControlSite的功能也实现一遍以使CDHtmlDialog的原有封装性不被破坏。在CMyControlSite中实现IDocHostUIHandler接口既可完成此功能。代码声明如下
1 public: 2 1 CMyControlSite(COleControlContainer *pCnt, CDHtmlDialog *pHandler):COleControlSite(pCnt),m_pHandler(pHandler) {} 3 1 protected: 4 ? 5 1 CDHtmlDialog *m_pHandler; 6 7 8 9 16 10 17 11 18 BEGIN_INTERFACE_PART(DocHostUIHandler, IDocHostUIHandler) 12 STDMETHOD(ShowContextMenu)(DWORD, LPPOINT, LPUNKNOWN, LPDISPATCH); 13 STDMETHOD(GetHostInfo)(DOCHOSTUIINFO*); 14 STDMETHOD(ShowUI)(DWORD, LPOLEINPLACEACTIVEOBJECT, 15 LPOLECOMMANDTARGET, LPOLEINPLACEFRAME, LPOLEINPLACEUIWINDOW); 16 STDMETHOD(HideUI)(void); 17 STDMETHOD(UpdateUI)(void); 18 STDMETHOD(EnableModeless)(BOOL); 19 STDMETHOD(OnDocWindowActivate)(BOOL); 20 STDMETHOD(OnFrameWindowActivate)(BOOL); 21 STDMETHOD(ResizeBorder)(LPCRECT, LPOLEINPLACEUIWINDOW, BOOL); 22 STDMETHOD(TranslateAccelerator)(LPMSG, const GUID*, DWORD); 23 STDMETHOD(GetOptionKeyPath)(OLECHAR **, DWORD); 24 STDMETHOD(GetDropTarget)(LPDROPTARGET, LPDROPTARGET*); 25 STDMETHOD(GetExternal)(LPDISPATCH*); 26 STDMETHOD(TranslateUrl)(DWORD, OLECHAR*, OLECHAR **); 27 STDMETHOD(FilterDataObject)(LPDATAOBJECT , LPDATAOBJECT*); 28 END_INTERFACE_PART(DocHostUIHandler)
以下是实现代码
1 BEGIN_INTERFACE_MAP(CMyControlSite, COleControlSite) 2 INTERFACE_PART(CMyControlSite, IID_IDocHostUIHandler, DocHostUIHandler) 3 INTERFACE_PART(CMyControlSite, IID_IOleCommandTarget, OleCommandTarget) 4 END_INTERFACE_MAP() 5 6 STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetExternal(LPDISPATCH *lppDispatch) 7 { 8 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 9 return pThis->m_pHandler->GetExternal(lppDispatch); 10 } 11 12 13 14 STDMETHODIMP CMyControlSite::XDocHostUIHandler::ShowContextMenu( 15 DWORD dwID, LPPOINT ppt, LPUNKNOWN pcmdTarget, LPDISPATCH pdispReserved) 16 { 17 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 18 return pThis->m_pHandler->ShowContextMenu(dwID, ppt, pcmdTarget, pdispReserved); 19 } 20 21 STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetHostInfo( 22 DOCHOSTUIINFO *pInfo) 23 { 24 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 25 return pThis->m_pHandler->GetHostInfo(pInfo); 26 } 27 28 29 STDMETHODIMP CMyControlSite::XDocHostUIHandler::ShowUI( 30 DWORD dwID, LPOLEINPLACEACTIVEOBJECT pActiveObject, 31 LPOLECOMMANDTARGET pCommandTarget, LPOLEINPLACEFRAME pFrame, 32 LPOLEINPLACEUIWINDOW pDoc) 33 { 34 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 35 return pThis->m_pHandler->ShowUI(dwID, pActiveObject, pCommandTarget, pFrame, pDoc); 36 } 37 38 STDMETHODIMP CMyControlSite::XDocHostUIHandler::HideUI(void) 39 { 40 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 41 return pThis->m_pHandler->HideUI(); 42 } 43 44 STDMETHODIMP CMyControlSite::XDocHostUIHandler::UpdateUI(void) 45 { 46 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 47 return pThis->m_pHandler->UpdateUI(); 48 } 49 50 51 STDMETHODIMP CMyControlSite::XDocHostUIHandler::EnableModeless(BOOL fEnable) 52 { 53 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 54 return pThis->m_pHandler->EnableModeless(fEnable); 55 } 56 57 STDMETHODIMP CMyControlSite::XDocHostUIHandler::OnDocWindowActivate(BOOL fActivate) 58 { 59 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 60 return pThis->m_pHandler->OnDocWindowActivate(fActivate); 61 } 62 63 STDMETHODIMP CMyControlSite::XDocHostUIHandler::OnFrameWindowActivate( 64 BOOL fActivate) 65 { 66 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 67 return pThis->m_pHandler->OnFrameWindowActivate(fActivate); 68 } 69 70 STDMETHODIMP CMyControlSite::XDocHostUIHandler::ResizeBorder( 71 LPCRECT prcBorder, LPOLEINPLACEUIWINDOW pUIWindow, BOOL fFrameWindow) 72 { 73 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 74 return pThis->m_pHandler->ResizeBorder(prcBorder, pUIWindow, fFrameWindow); 75 } 76 77 STDMETHODIMP CMyControlSite::XDocHostUIHandler::TranslateAccelerator( 78 LPMSG lpMsg, const GUID* pguidCmdGroup, DWORD nCmdID) 79 { 80 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 81 return pThis->m_pHandler->TranslateAccelerator(lpMsg, pguidCmdGroup, nCmdID); 82 } 83 84 85 STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetOptionKeyPath( 86 LPOLESTR* pchKey, DWORD dwReserved) 87 { 88 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 89 return pThis->m_pHandler->GetOptionKeyPath(pchKey, dwReserved); 90 } 91 92 93 STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetDropTarget( 94 LPDROPTARGET pDropTarget, LPDROPTARGET* ppDropTarget) 95 { 96 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 97 return pThis->m_pHandler->GetDropTarget(pDropTarget, ppDropTarget); 98 } 99 100 STDMETHODIMP CMyControlSite::XDocHostUIHandler::TranslateUrl( 101 DWORD dwTranslate, OLECHAR* pchURLIn, OLECHAR** ppchURLOut) 102 { 103 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 104 return pThis->m_pHandler->TranslateUrl(dwTranslate, pchURLIn, ppchURLOut); 105 } 106 107 STDMETHODIMP CMyControlSite::XDocHostUIHandler::FilterDataObject( 108 LPDATAOBJECT pDataObject, LPDATAOBJECT* ppDataObject) 109 { 110 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 111 return pThis->m_pHandler->FilterDataObject(pDataObject, ppDataObject); 112 } 113 114 115 STDMETHODIMP_(ULONG) CMyControlSite::XDocHostUIHandler::AddRef() 116 { 117 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 118 return pThis->ExternalAddRef(); 119 } 120 121 STDMETHODIMP_(ULONG) CMyControlSite::XDocHostUIHandler::Release() 122 { 123 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 124 return pThis->ExternalRelease(); 125 } 126 127 STDMETHODIMP CMyControlSite::XDocHostUIHandler::QueryInterface( 128 REFIID iid, LPVOID far* ppvObj) 129 { 130 METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler) 131 return pThis->ExternalQueryInterface(&iid, ppvObj); 132 }
STDMETHODIMP宏的定义是HRESULT __stdcall,STDMETHODIMP_宏指定了返回值,这两个宏用在cpp实现文件中,对应用于声明时使用的STDMETHOD和STDMETHOD_。
METHOD_PROLOGUE_EX_宏定义了pThis指针来指向外层类。