当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接口的三个方法AddRefReleaseQueryInterfaceEND_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的类实现代码


点击(此处)折叠或打开

  1. BEGIN_INTERFACE_MAP(CMyControlSite, COleControlSite)
  2.     INTERFACE_PART(CMyControlSite, IID_IOleCommandTarget, OleCommandTarget)
  3. END_INTERFACE_MAP()
  4.   
  5.   
  6. HRESULT CMyControlSite::XOleCommandTarget::Exec
  7. (const GUID* pguidCmdGroup, DWORD nCmdID,
  8.  DWORD nCmdexecopt, VARIANTARG* pvaIn, VARIANTARG* pvaOut )
  9. {
  10.     HRESULT hr = OLECMDERR_E_NOTSUPPORTED;
  11.     //return S_OK;
  12.     if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CGID_DocHostCommandHandler))
  13.     {
  14.   
  15.         switch (nCmdID)
  16.         {
  17.   
  18.         case OLECMDID_SHOWSCRIPTERROR:
  19.             {
  20.                 IHTMLDocument2* pDoc = NULL;
  21.                 IHTMLWindow2* pWindow = NULL;
  22.                 IHTMLEventObj* pEventObj = NULL;
  23.                 BSTR rgwszNames[5] =
  24.                 {
  25.                     SysAllocString(L"errLine"),
  26.                     SysAllocString(L"errCharacter"),
  27.                     SysAllocString(L"errCode"),
  28.                     SysAllocString(L"errMsg"),
  29.                     SysAllocString(L"errUrl")
  30.                 };
  31.                 DISPID rgDispIDs[5];
  32.                 VARIANT rgvaEventInfo[5];
  33.                 DISPPARAMS params;
  34.                 BOOL fContinueRunningScripts = true;
  35.   
  36.                 params.cArgs = 0;
  37.                 params.cNamedArgs = 0;
  38.                   
  39.                 hr = pvaIn->punkVal->QueryInterface(IID_IHTMLDocument2, (void **) &pDoc);
  40.                    
  41.                 hr = pDoc->get_parentWindow(&pWindow);
  42.                 pDoc->Release();
  43.                   
  44.                 hr = pWindow->get_event(&pEventObj);
  45.                   
  46.                 for (int i = 0; i < 5; i++)
  47.                 {
  48.                       
  49.                     hr = pEventObj->GetIDsOfNames(IID_NULL, &rgwszNames[i], 1,
  50.                         LOCALE_SYSTEM_DEFAULT, &rgDispIDs[i]);
  51.                   
  52.                     hr = pEventObj->Invoke(rgDispIDs[i], IID_NULL,
  53.                         LOCALE_SYSTEM_DEFAULT,
  54.                         DISPATCH_PROPERTYGET, ?ms, &rgvaEventInfo[i],
  55.                         NULL, NULL);
  56.                     //可以在此记录错误信息
  57.                     //必须使用SysFreeString来释放SysAllocString分配的内存,SysAllocString在分配的内存中记录了字符的长度
  58.                     SysFreeString(rgwszNames[i]);
  59.                 }
  60.   
  61.                 // At this point, you would normally alert the user with
  62.                 // the information about the error, which is now contained
  63.                 // in rgvaEventInfo[]. Or, you could just exit silently.
  64.   
  65.                 (*pvaOut).vt = VT_BOOL;
  66.                 if (fContinueRunningScripts)
  67.                 {
  68.                     // 在页面中继续执行脚本
  69.                     (*pvaOut).boolVal = VARIANT_TRUE;
  70.                 }
  71.                 else
  72.                 {
  73.                     // 停止在页面中执行脚本
  74.                     (*pvaOut).boolVal = VARIANT_FALSE;
  75.                 }
  76.                 break;
  77.             }
  78.         default:
  79.             hr =OLECMDERR_E_NOTSUPPORTED;
  80.             break;
  81.         }
  82.     }
  83.     else
  84.     {
  85.         hr = OLECMDERR_E_UNKNOWNGROUP;
  86.     }
  87.     return (hr);
  88. }
  89.   
  90.   
  91. ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::AddRef()
  92. {
  93.     METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)
  94.         return pThis->ExternalAddRef();
  95. }
  96.   
  97.   
  98. ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::Release()
  99. {
  100.     METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)
  101.         return pThis->ExternalRelease();
  102. }
  103.   
  104. HRESULT FAR EXPORT CMyControlSite::XOleCommandTarget::QueryInterface(REFIID riid, void **ppvObj)
  105. {
  106.     METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)
  107.         HRESULT hr = (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObj);
  108.     return hr;
  109. }
  110.   
  111. STDMETHODIMP CMyControlSite::XOleCommandTarget::QueryStatus(
  112.     /* [unique][in] */ const GUID __RPC_FAR *pguidCmdGroup,
  113.     /* [in] */ ULONG cCmds,
  114.     /* [out][in][size_is] */ OLECMD __RPC_FAR prgCmds[ ],
  115.     /* [unique][out][in] */ OLECMDTEXT __RPC_FAR *pCmdText
  116.     )
  117. {
  118.     METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)
  119.         return OLECMDERR_E_NOTSUPPORTED;
  120. }

实现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.     CMyControlSite(COleControlContainer *pCnt, CDHtmlDialog *pHandler):COleControlSite(pCnt),m_pHandler(pHandler) {}
  3. protected:
  4.    CDHtmlDialog *m_pHandler;

  5. BEGIN_INTERFACE_PART(DocHostUIHandler, IDocHostUIHandler)
  6.         STDMETHOD(ShowContextMenu)(DWORD, LPPOINT, LPUNKNOWN, LPDISPATCH);
  7.         STDMETHOD(GetHostInfo)(DOCHOSTUIINFO*);
  8.         STDMETHOD(ShowUI)(DWORD, LPOLEINPLACEACTIVEOBJECT, LPOLECOMMANDTARGET, LPOLEINPLACEFRAME, LPOLEINPLACEUIWINDOW);
  9.         STDMETHOD(HideUI)(void);
  10.         STDMETHOD(UpdateUI)(void);
  11.         STDMETHOD(EnableModeless)(BOOL);
  12.         STDMETHOD(OnDocWindowActivate)(BOOL);
  13.         STDMETHOD(OnFrameWindowActivate)(BOOL);
  14.         STDMETHOD(ResizeBorder)(LPCRECT, LPOLEINPLACEUIWINDOW, BOOL);
  15.         STDMETHOD(TranslateAccelerator)(LPMSG, const GUID*, DWORD);
  16.         STDMETHOD(GetOptionKeyPath)(OLECHAR **, DWORD);
  17.         STDMETHOD(GetDropTarget)(LPDROPTARGET, LPDROPTARGET*);
  18.         STDMETHOD(GetExternal)(LPDISPATCH*);
  19.         STDMETHOD(TranslateUrl)(DWORD, OLECHAR*, OLECHAR **);
  20.         STDMETHOD(FilterDataObject)(LPDATAOBJECT , LPDATAOBJECT*);
  21.     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 __stdcallSTDMETHODIMP_宏指定了返回值,这两个宏用在cpp实现文件中,对应用于声明时使用的STDMETHODSTDMETHOD_

METHOD_PROLOGUE_EX_宏定义了pThis指针来指向外层类。

 

以上代码基于VS2008,由于不同版本的VS所带的MFC库版本不尽一致所以需要根本具体的版本来处理,目前已知的不同部分主要集中在CreateControlSite上。





10-20 22:03