• CDHtmlDialog探索WebBrowser扩展和网页Javascript错误处理


    当WebBrowser控件(CDHtmlDialog自动创建了WebBrowser控件)加载的网页中含有错误Javascript代码时默认情况下控件会弹出错误信息提示对话框,相对于用户体验来说这样的提示完全不是开发人员想要的,针对这个问题有两个解决方案,一是完全屏蔽掉错误提示,二是控制错误的提示并且记录错误信息同时也可以控制出现错误后Javascript是否继续执行。

    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,这样就可以得到错误信息了。

    IOleCommandTarget : public IUnknown
        {
        public:
            virtual /* [input_sync] */ HRESULT STDMETHODCALLTYPE QueryStatus( 
                /* [unique][in] */ __RPC__in_opt const GUID *pguidCmdGroup,
                /* [in] */ ULONG cCmds,
                /* [out][in][size_is] */ __RPC__inout_ecount_full(cCmds) OLECMD prgCmds[  ],
                /* [unique][out][in] */ __RPC__inout_opt OLECMDTEXT *pCmdText) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE Exec( 
                /* [unique][in] */ __RPC__in_opt const GUID *pguidCmdGroup,
                /* [in] */ DWORD nCmdID,
                /* [in] */ DWORD nCmdexecopt,
                /* [unique][in] */ __RPC__in_opt VARIANT *pvaIn,
                /* [unique][out][in] */ __RPC__inout_opt VARIANT *pvaOut) = 0;
            
        };
        
    

    现在我们开始实现自定义的COleControlSite

    class CMyControlSite : public COleControlSite 
    {  
    
    public:  
    	CMyControlSite(COleControlContainer *pCntr):COleControlSite(pCntr) {}
    
    protected:  
    
    	DECLARE_INTERFACE_MAP()  
    	BEGIN_INTERFACE_PART(OleCommandTarget, IOleCommandTarget)  
    		STDMETHOD(QueryStatus)(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD prgCmds[], OLECMDTEXT *pCmdText);  
    		STDMETHOD(Exec)(const GUID* pguidCmdGroup, DWORD nCmdID, DWORD nCmdexecopt, VARIANTARG* pvaIn, VARIANTARG* pvaOut);  
    	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的类实现代码

     BEGIN_INTERFACE_MAP(CMyControlSite, COleControlSite)   INTERFACE_PART(CMyControlSite, IID_IOleCommandTarget, OleCommandTarget)

    END_INTERFACE_MAP()  
    
    
    HRESULT CMyControlSite::XOleCommandTarget::Exec  
    (const GUID* pguidCmdGroup, DWORD nCmdID,  
     DWORD nCmdexecopt, VARIANTARG* pvaIn, VARIANTARG* pvaOut )  
    {  
    	HRESULT hr = OLECMDERR_E_NOTSUPPORTED;  
    	//return S_OK;  
    	if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CGID_DocHostCommandHandler))  
    	{  
    
    		switch (nCmdID)   
    		{  
    
    		case OLECMDID_SHOWSCRIPTERROR:  
    			{  
    				IHTMLDocument2*             pDoc = NULL;  
    				IHTMLWindow2*               pWindow = NULL;  
    				IHTMLEventObj*              pEventObj = NULL;  
    				BSTR                        rgwszNames[5] =   
    				{   
    					SysAllocString(L"errorLine"), 
    SysAllocString(L"errorCharacter"), 
    SysAllocString(L"errorCode"), 
    SysAllocString(L"errorMessage"), 
    SysAllocString(L"errorUrl") }; DISPID rgDispIDs[5]; VARIANT rgvaEventInfo[5]; DISPPARAMS params; BOOL fContinueRunningScripts = true; params.cArgs = 0; params.cNamedArgs = 0; hr = pvaIn->punkVal->QueryInterface(IID_IHTMLDocument2, (void **) &pDoc); hr = pDoc->get_parentWindow(&pWindow); pDoc->Release(); hr = pWindow->get_event(&pEventObj); for (int i = 0; i < 5; i++) { hr = pEventObj->GetIDsOfNames(IID_NULL, &rgwszNames[i], 1, LOCALE_SYSTEM_DEFAULT, &rgDispIDs[i]); hr = pEventObj->Invoke(rgDispIDs[i], IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, ¶ms, &rgvaEventInfo[i], NULL, NULL); //可以在此记录错误信息
    					//必须使用SysFreeString来释放SysAllocString分配的内存,SysAllocString在分配的内存中记录了字符的长度
    					SysFreeString(rgwszNames[i]);  
    				}  
    
    				// At this point, you would normally alert the user with   
    				// the information about the error, which is now contained  
    				// in rgvaEventInfo[]. Or, you could just exit silently.  
    
    				(*pvaOut).vt = VT_BOOL;  
    				if (fContinueRunningScripts)  
    				{  
    					// 在页面中继续执行脚本 
    					(*pvaOut).boolVal = VARIANT_TRUE;  
    				}  
    				else 
    				{  
    					// 停止在页面中执行脚本  
    					(*pvaOut).boolVal = VARIANT_FALSE;     
    				}   
    				break;  
    			}  
    		default:  
    			hr =OLECMDERR_E_NOTSUPPORTED; 
    			break;  
    		}  
    	}  
    	else 
    	{  
    		hr = OLECMDERR_E_UNKNOWNGROUP;
    	}  
    	return (hr);  
    }  
    
    
    ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::AddRef()   
    {   
    	METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)   
    		return pThis->ExternalAddRef();   
    }   
    
    
    ULONG FAR EXPORT CMyControlSite::XOleCommandTarget::Release()   
    {   
    	METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)   
    		return pThis->ExternalRelease();   
    }   
    
    HRESULT FAR EXPORT CMyControlSite::XOleCommandTarget::QueryInterface(REFIID riid, void **ppvObj)   
    {   
    	METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)   
    		HRESULT hr = (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObj);   
    	return hr;   
    }  
    
    STDMETHODIMP CMyControlSite::XOleCommandTarget::QueryStatus(   
    	/* [unique][in] */ const GUID __RPC_FAR *pguidCmdGroup,   
    	/* [in] */ ULONG cCmds,   
    	/* [out][in][size_is] */ OLECMD __RPC_FAR prgCmds[ ],   
    	/* [unique][out][in] */ OLECMDTEXT __RPC_FAR *pCmdText   
    	)   
    {   
    	METHOD_PROLOGUE(CMyControlSite, OleCommandTarget)   
    		return OLECMDERR_E_NOTSUPPORTED;   
    }   
    
    

    实现CMyControlSite后需要应用到CDHtmlDialog上,重写CreateControlSite虚函数既可

     

    virtual BOOL CreateControlSite(COleControlContainer* pContainer, 
    		COleControlSite** ppSite, UINT  nID , REFCLSID  clsid )
    {
            if(ppSite == NULL)
    	{
    		ASSERT(FALSE);
    		return FALSE;
    	}
    
    	CMyControlSite *pBrowserSite = 
    		new CMyControlSite (pContainer, this);
    	if (!pBrowserSite)
    		return FALSE;
    
    	*ppSite = pBrowserSite;
    	return TRUE;
    }
    

    现在就可以去编译测试了。到目前还有一个问题没有考虑,如果这段代码整合到现有的CDHtmlDialog应用中而现有的代码使用了其它默认的设定比如说自定义WebBrowser的右健菜单或使用了GetIDispatch函数等情况下原有的代码就不能正常工作了。这部分功能是在MFC的CBrowserControlSite类中处理的,所以CMyControlSite应该把CBrowserControlSite的功能也实现一遍以使CDHtmlDialog的原有封装性不被破坏。在CMyControlSite中实现IDocHostUIHandler接口既可完成此功能。代码声明如下

    public:
      CMyControlSite(COleControlContainer *pCnt, CDHtmlDialog *pHandler):COleControlSite(pCnt),m_pHandler(pHandler) {}
    protected:
      CDHtmlDialog *m_pHandler;
    BEGIN_INTERFACE_PART(DocHostUIHandler, IDocHostUIHandler)
    		STDMETHOD(ShowContextMenu)(DWORD, LPPOINT, LPUNKNOWN, LPDISPATCH);
    		STDMETHOD(GetHostInfo)(DOCHOSTUIINFO*);
    		STDMETHOD(ShowUI)(DWORD, LPOLEINPLACEACTIVEOBJECT,
    			LPOLECOMMANDTARGET, LPOLEINPLACEFRAME, LPOLEINPLACEUIWINDOW);
    		STDMETHOD(HideUI)(void);
    		STDMETHOD(UpdateUI)(void);
    		STDMETHOD(EnableModeless)(BOOL);
    		STDMETHOD(OnDocWindowActivate)(BOOL);
    		STDMETHOD(OnFrameWindowActivate)(BOOL);
    		STDMETHOD(ResizeBorder)(LPCRECT, LPOLEINPLACEUIWINDOW, BOOL);
    		STDMETHOD(TranslateAccelerator)(LPMSG, const GUID*, DWORD);
    		STDMETHOD(GetOptionKeyPath)(OLECHAR **, DWORD);
    		STDMETHOD(GetDropTarget)(LPDROPTARGET, LPDROPTARGET*);
    		STDMETHOD(GetExternal)(LPDISPATCH*);
    		STDMETHOD(TranslateUrl)(DWORD, OLECHAR*, OLECHAR **);
    		STDMETHOD(FilterDataObject)(LPDATAOBJECT , LPDATAOBJECT*);
    	END_INTERFACE_PART(DocHostUIHandler)
    

    以下是实现代码

    BEGIN_INTERFACE_MAP(CMyControlSite, COleControlSite)  
    	INTERFACE_PART(CMyControlSite, IID_IDocHostUIHandler, DocHostUIHandler)
    	INTERFACE_PART(CMyControlSite, IID_IOleCommandTarget, OleCommandTarget)
    END_INTERFACE_MAP()  
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetExternal(LPDISPATCH *lppDispatch)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->GetExternal(lppDispatch);
    }
    
    
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::ShowContextMenu(
    	DWORD dwID, LPPOINT ppt, LPUNKNOWN pcmdTarget, LPDISPATCH pdispReserved)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->ShowContextMenu(dwID, ppt, pcmdTarget, pdispReserved);
    }
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetHostInfo(
    	DOCHOSTUIINFO *pInfo)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->GetHostInfo(pInfo);
    }
    
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::ShowUI(
    	DWORD dwID, LPOLEINPLACEACTIVEOBJECT pActiveObject,
    	LPOLECOMMANDTARGET pCommandTarget, LPOLEINPLACEFRAME pFrame,
    	LPOLEINPLACEUIWINDOW pDoc)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->ShowUI(dwID, pActiveObject, pCommandTarget, pFrame, pDoc);
    }
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::HideUI(void)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->HideUI();
    }
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::UpdateUI(void)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->UpdateUI();
    }
    
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::EnableModeless(BOOL fEnable)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->EnableModeless(fEnable);
    }
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::OnDocWindowActivate(BOOL fActivate)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->OnDocWindowActivate(fActivate);
    }
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::OnFrameWindowActivate(
    	BOOL fActivate)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->OnFrameWindowActivate(fActivate);
    }
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::ResizeBorder(
    	LPCRECT prcBorder, LPOLEINPLACEUIWINDOW pUIWindow, BOOL fFrameWindow)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->ResizeBorder(prcBorder, pUIWindow, fFrameWindow);
    }
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::TranslateAccelerator(
    	LPMSG lpMsg, const GUID* pguidCmdGroup, DWORD nCmdID)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->TranslateAccelerator(lpMsg, pguidCmdGroup, nCmdID);
    }
    
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetOptionKeyPath(
    	LPOLESTR* pchKey, DWORD dwReserved)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->GetOptionKeyPath(pchKey, dwReserved);
    }
    
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::GetDropTarget(
    	LPDROPTARGET pDropTarget, LPDROPTARGET* ppDropTarget)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->GetDropTarget(pDropTarget, ppDropTarget);
    }
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::TranslateUrl(
    	DWORD dwTranslate, OLECHAR* pchURLIn, OLECHAR** ppchURLOut)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->TranslateUrl(dwTranslate, pchURLIn, ppchURLOut);
    }
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::FilterDataObject(
    	LPDATAOBJECT pDataObject, LPDATAOBJECT* ppDataObject)
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    	return pThis->m_pHandler->FilterDataObject(pDataObject, ppDataObject);
    }
    
    
    STDMETHODIMP_(ULONG) CMyControlSite::XDocHostUIHandler::AddRef()
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    		return pThis->ExternalAddRef();
    }
    
    STDMETHODIMP_(ULONG) CMyControlSite::XDocHostUIHandler::Release()
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    		return pThis->ExternalRelease();
    }
    
    STDMETHODIMP CMyControlSite::XDocHostUIHandler::QueryInterface(
    	REFIID iid, LPVOID far* ppvObj)     
    {
    	METHOD_PROLOGUE_EX_(CMyControlSite, DocHostUIHandler)
    		return pThis->ExternalQueryInterface(&iid, ppvObj);
    }
    

    STDMETHODIMP宏的定义是HRESULT __stdcallSTDMETHODIMP_宏指定了返回值,这两个宏用在cpp实现文件中,对应用于声明时使用的STDMETHODSTDMETHOD_

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

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

  • 相关阅读:
    SQL Server CHARINDEX和PATINDEX详解
    MVC ListBoxFor raises “value cannot be null” exception
    jquery.uploadify动态传递表单元素
    C# 判断一字符串是否为合法数字(正则表达式)
    jquery 操作Listbox
    JQuery 操作 ListBox间移动和ListBox内移动
    jQuery获取Select选择的Text和 Value
    SQL 语句的执行效率
    JSON 序列化长度限制问题
    C#中利用FileSystemWatcher对单个文件内容的监视
  • 原文地址:https://www.cnblogs.com/thinkingfor/p/1873689.html
Copyright © 2020-2023  润新知