The goal of this project is to replace web browser wrapper control with one that allows developers to create and use a web browser control having total control over GUI, context menus, accelerator keys, downloads, security, ... without using any sub classing, registry or hacks. The result is an ATL control:
- Which is as easy to use as any web browser wrapper control. Register vbMHWB.dll and use it as any other ActiveX control.
- Allows viewing of all request headers (HTML, images, CSS, ...) with the option of adding additional headers (HTTP + HTTPS).
- Allows viewing of all response headers (HTTP + HTTPS).
- Allows GUI customization using
DOC_HOST_UI_FLAGS
per web browser control instance or globally. NO3DBORDER, ...
- Allows behavior customization using
DOC_DOWNLOAD_CONTROL_FLAGS
per web browser control instance or globally. DLIMAGES
, DLVIDEOS
, ...
- Disallows context menus or raises
OnContextMenu
event for each context menu activated.
- Disallows accelerator keys or raises
OnAcceletorKeys
event for each accelerator key activated.
- That, by default, is configured to take over user downloads using
FileDownloadEx
and OnFileDLxxxx
events.
- That can be used as a simple download manager using
DownloadUrlAsync
method and OnFileDLxxx
events.
- Allows fine tuning of security per URL via
SecurityManagerProcessUrlAction
event.
- Allows interception and overriding of HTTP security problems via
OnHTTPSecurityProblem
event.
- Allows interception and overriding of basic authentication requests via
OnAuthentication
event.
- Allows replacing or augmenting registry settings via
OnGetOptionKeyPath
and OnGetOverrideKeyPath
events.
- Allows posting of data via
GET
or POST
methods with notifications via OnPostxxxx
events.
- Allows handling of single or multiple drops via
OnWBDragxxx
and OnWBDropx
events.
- That adds a host of new properties, methods and events, in addition to almost any web browser wrapper control's properties, methods and events.
The control is written in VC++ 6.0 using ATL 3.0. It is compiled with minimum dependencies (no MFC, std::
, CString
, ...). It is designed to host multiple web browser controls within one ATL created window. This is contradictory to MSDN recommendation which suggests to use one ATL window per hosted control. The reason for choosing this approach was to remove the burden of web browser control management from the hosting client application to the control. Normally, a developer places an instance of a control on a form/dialog, then if needed, an array of the controls is created and maintained by the client application. My approach enables the developer to insert one instance of this control on a form/dialog and then use CvbWB::AddBrowser
and CvbWB::RemoveBrowser
methods to add and remove web browser controls. Each newly created control (through an instance of IWB
class) is assigned a unique ID (wbUID)
. This unique ID enables the client application to communicate with that specific web browser control instance via its properties, methods, and to find out which web browser control has fired an event.
Even though this control was made to be used by VB, due to the fact that it is a fully compliant ActiveX control, it can also be used from MFC.
Index
Implementation challenges
There are many articles that cover the basics of creating, hosting and sinking events of a web browser control. So rather than going through CoCreateInstance
, IOleObject::SetClientSite
, and so on, I decided to explain some of the main implementation challenges where you will find very little and often no information about them.
Here is a list of the main challenges encountered and resolved during the development of this control. I neither claim that these solutions are unique nor the best. Just that they seem to work.
- The first issue that I encountered was lack of documentation on how to pass the
byref
parameter or objects to a client application such as VB. Attempts to handle any of these wizard generated events was causing GPF. Here is a sample of the non-working code for NewWindow3
event taken from the CProxy_IvbWBEvents
class: VOID Fire_NewWindow3(SHORT wbUID, IDispatch * * ppDisp,
VARIANT_BOOL * Cancel, LONG lFlags,
BSTR sURLContext, BSTR sURL)
{
T* pT = static_cast<T*>(this);
int nConnectionIndex;
CComVariant* pvars = new CComVariant[6];
int nConnections = m_vec.GetSize();
for (nConnectionIndex = 0; nConnectionIndex < nConnections;
nConnectionIndex++)
{
pT->Lock();
CComPtr<IUnknown> sp = m_vec.GetAt(nConnectionIndex);
pT->Unlock();
IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
if (pDispatch != NULL)
{
pvars[5] = wbUID;
pvars[4] = ppDisp;
pvars[3] = Cancel;
pvars[2] = lFlags;
pvars[1] = sURLContext;
pvars[0] = sURL;
DISPPARAMS disp = { pvars, NULL, 6, 0 };
pDispatch->Invoke(0x2d, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, &disp, NULL, NULL, NULL);
}
}
}
And here is the correction:
pvars[4].vt = VT_BYREF|VT_DISPATCH;
pvars[4].byref = ppDisp;
pvars[3].vt = VT_BOOL|VT_BYREF;
pvars[3].byref = Cancel;
- The second issue that I encountered was again related to events. This issue showed up when I implemented the protocol handlers. Apparently, the
IConnectionPointImpl
does not fire events across COM components. So, after some looking around, I came across the KB article 280512, ATLCPImplMT
encapsulates the ATL event firing across COM apartments. Using the included class IConnectionPointImplMT
(from MS) solved this issue. And here is the completed code for the Newwindow3
event: VOID Fire_NewWindow3(SHORT wbUID, IDispatch * * ppDisp,
VARIANT_BOOL * Cancel, LONG lFlags,
BSTR sURLContext, BSTR sURL)
{
T* pT = static_cast<T*>(this);
int nConnectionIndex;
CComVariant* pvars = new CComVariant[6];
int nConnections = m_vec.GetSize();
for (nConnectionIndex = 0; nConnectionIndex < nConnections;
nConnectionIndex++)
{
CComPtr<IUnknown> sp;
sp.Attach (GetInterfaceAt(nConnectionIndex));
IDispatch* pDispatch = reinterpret_cast<IDispatch*>(sp.p);
if (pDispatch != NULL)
{
pvars[5] = wbUID;
pvars[4].vt = VT_BYREF|VT_DISPATCH;
pvars[4].byref = ppDisp;
pvars[3].vt = VT_BOOL|VT_BYREF;
pvars[3].byref = Cancel;
pvars[2] = lFlags;
pvars[1] = sURLContext;
pvars[0] = sURL;
DISPPARAMS disp = { pvars, NULL, 6, 0 };
pDispatch->Invoke(0x2d, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD, &disp, NULL, NULL, NULL);
}
}
}
- The third issue showed up after implementing the protocol handler. I needed to fire events from the
WBPassthruSink
(protocol handler sink) class for a specific web browser control to notify the client application via ProtocolHandlerOnBeginTransaction
and ProtocolHandlerOnResponse
events. Since the instances of the WBPassthruSink
class are created by URLMon
as needed by PassthroughAPP
, I had to find a way to determine which instance of the web browser control is involved so that I can fire events for that specific control. My solution was to find the Internet Explorer Server HWND
in the implementation of WBPassthruSink::OnStart
using the IWindowForBindingUI
interface obtained from our protocol handler:
CComPtr<IWindowForBindingUI> objWindowForBindingUI;
HRESULT hret = QueryServiceFromClient(&objWindowForBindingUI);
if( (SUCCEEDED(hret)) && (objWindowForBindingUI) )
{
HWND hwndIEServer = NULL;
objWindowForBindingUI->GetWindow(IID_IHttpSecurity,
&hwndIEServer);
if(hwndIEServer)
Index
One of the design goals of this control was to be build with minimum dependencies. Standard _ATL_MIN_CRT
support does the job for eliminating the CRT overhead well. But unfortunately, it doesn't support the use of global C++ constructs, like the following:
class CTest {
public:
CTest() {
MessageBox(NULL, _T("Hello, I'm intitialized"),
_T("Static object"), MB_SETFOREGROUND | MB_OK);
}
~CTest() {
MessageBox(NULL, _T("Bye, I'm done"), _T("Static object"),
MB_SETFOREGROUND | MB_OK);
}
};
static CTest g_test;
extern CTest *gp_Test;
The above would lead to linker conflicts, because the CRT code for constructors/destructors invocation would be referenced. To overcome this, I am using AuxCrt.cpp custom _ATL_MIN_CRT
implementation and a replacement for the AtlImpl.cpp class. This class is from Andrew Nosenko (andien@geocities.com). With this class, I was able to use CSimpleArray
as a global variable to keep track of the instances of web browser controls. Note, for convenience, I placed a copy of AuxCrt.cpp in the ATL\Include\ directory.
extern CSimpleArray<void*> gCtrlInstances;
extern BOOL gb_IsHttpRegistered;
extern BOOL gb_IsHttpsRegistered;
extern CComPtr<IClassFactory> m_spCFHTTP;
extern CComPtr<IClassFactory> m_spCFHTTPS;
....
Index
One of my main design goals was to be able to act as a pass through between a web browser control and URLMon
so as to intercept all the requests and responses by using an asynchronous pluggable protocol. At the start, this task seemed pretty straightforward, implementing IInternetProtocol
, IInternetProtocolInfo
, IInternetPriority
, IInternetProtocolSink
, IInternetBindInfo
, and IClassFactory
interfaces. In the implementation of the IServiceProvider::QueryService
, create and pass an instance of my IInternetProtocolImpl
to URLMon
. Overwrite the necessary methods and handle the requests. Unfortunately, this approach had a big flaw. My IInternetProtocol
implementation was only being called to handle the main document and not for the rest of the page requests, images, CSS,...
After doing some searching, I came across an excellent package called PassthroughAPP by Igor Tandetnik. In Google groups, under microsoft.public.inetsdk.programming.xxx, just search for PassthroughAPP. The package contains five files:
PassthroughObject.h |
A simple COM object IPassthroughObject . |
ProtocolCF.h |
A customized COM class factory, implements CComClassFactory . |
ProtocolCF.inl |
Class factory implementation. |
ProtocolImpl.h |
Protocol handlers header. Implemented interfaces:
IPassthroughObject
IInternetProtocol
IInternetProtocolInfo
IInternetPriority
IInternetThreadSwitch
IWinInetHttpInfo
IInternetProtocolSink
IServiceProvider
IInternetBindInfo |
ProtocolImpl.inl |
Protocol handlers implementation. |
Except for the five methods that I have overridden in the WBPassthruSink
class to intercept all the requests and response, I will not be able to answer any questions regarding PassthroughAPP. Please direct your questions to the author as my understanding of the package's design and implementation is limited.
Index
One of the design goals of this control was to take full control over file downloads. This was pretty simple to implement at first and all seemed to work without a glitch. But as usual, a strange problem was reported. If a user attempted to download an attachment from Hotmail, Yahoo!, ... the default download dialog was being displayed, bypassing my custom download manager. I traced the problem to RegisterBindStatusCallback
method which is called in IDownloadManager::Download
method, it was returning E_FAIL
. This failure seems to occur when a server sends a content-disposition header in response to a file download request. Of course, MSDN does not even mention anything about an E_FAIL
return or why RegisterBindStatusCallback
might fail. After searching for a while to no avail, I decided to attempt to implement a workaround.
The first step was to somehow force RegisterBindStatusCallback
to succeed:
- Call
RegisterBindStatusCallback
, passing an IBindStatusCallback
pointer to retrieve the previous IBindStatusCallback
.
- If the return value is
E_FAIL
, then call RevokeObjectParam
to un-register the previous IBindStatusCallback
.
- If the return value from
RevokeObjectParam
indicates success, attempt to call RegisterBindStatusCallback
for a second time which should succeed.
IBindStatusCallback *pPrevBSCB = NULL;
hr = RegisterBindStatusCallback(pbc,
reinterpret_cast<IBindStatusCallback*>(filedl), &pPrevBSCB, 0L);
if( (FAILED(hr)) && (pPrevBSCB) )
{
LPOLESTR oParam = L"_BSCB_Holder_";
hr = pbc->RevokeObjectParam(oParam);
if(SUCCEEDED(hr))
{
hr = RegisterBindStatusCallback(pbc,
reinterpret_cast<IBindStatusCallback*>(filedl), 0, 0L);
if(SUCCEEDED(hr))
{
filedl->m_pPrevBSCB = pPrevBSCB;
filedl->AddRef();
pPrevBSCB->AddRef();
filedl->m_pBindCtx = pbc;
pbc->AddRef();
}
The second step was to relay some calls to the previous IBindStatusCallback
from our implementation. Otherwise, no download will take place. Memory leaks and crashes are to be expected. This part was based on trial and error.
- In
::OnStartBinding
: if(m_pPrevBSCB)
{
m_pPrevBSCB->OnStopBinding(HTTP_STATUS_OK, NULL);
}
- In
::OnProgress
: if(m_pPrevBSCB)
{
if(ulStatusCode == BINDSTATUS_CONTENTDISPOSITIONATTACH)
return S_OK;
m_pPrevBSCB->OnProgress(ulProgress, ulProgressMax,
ulStatusCode, szStatusText);
}
- In
::OnStopBinding
: if( (m_pPrevBSCB) && (m_pBindCtx) )
{
LPOLESTR oParam = L"_BSCB_Holder_";
m_pBindCtx->RegisterObjectParam(oParam,
reinterpret_cast(m_pPrevBSCB));
m_pPrevBSCB->Release();
m_pPrevBSCB = NULL;
m_pBindCtx->Release();
m_pBindCtx = NULL;
--m_cRef;
}
Index
Please ensure that you have the latest SDK that works with VC++ 6.0, February 2003 SDK, and IE6 headers and libraries.
Class name |
Implements |
Description |
CvbWB |
CComObjectRootEx
IDispatchImpl
CComControl
IPersistStreamInitImpl
IOleControlImpl
IOleObjectImpl
IOleInPlace ActiveObjectImpl *
IViewObjectExImpl
IOleInPlaceObject WindowlessImpl *
ISupportErrorInfo
IConnectionPoint ContainerImpl *
IPersistStorageImpl
ISpecify PropertyPagesImpl *
IQuickActivateImpl
IDataObjectImpl
IProvideClassInfo2Impl
IPropertyNotifySinkCP
CComCoClass
CProxy_IvbWBEvents |
This class was created as a full ATL control using the wizard. It is responsible to host the control in a client application (VB, C++), fire events, and allow access to properties and methods of all web browser controls to the hosting client. This task is achieved by using a simple array of IWB pointers. The pointers are added and removed from the array by calls to AddBrowser and RemoveBrowser methods. Each new instance of IWB is given a unique ID wbUID . The client application uses this ID to access this instance of the web browser control's properties and methods. Also, all the events come with an extra parameter, wbUID , which identifies the web browser instance that has fired the event. In addition, this class contains a number of useful methods and properties, get_ActiveDocumentObj , get_ActiveElementObj (returns active document or element; accounts for frames), DownloadUrlAsync (starts a file download, allowing client app to monitor the status via OnFileDL_xxx events), ucInternetCrackUrl (to break a given URL into parts including filename and extension, and the parts are accessed via Get/Set ucXXX properties),... |
IWB |
IUnknown |
This class is responsible to create and maintain a single instance of a web browser control along with all the necessary classes such as WBClientSite (IOleClientSite implementation). Also, all the QIs from all the classes are routed through the same IWB instance that created them. In addition, it contains a number of useful methods, IsFrameset , FramesCount , DocHighlightFindText ,... |
WBClientSite |
IOleClientSite |
Required as part of web browser control hosting interfaces. All methods return E_NOTIMPL . |
WBInPlaceSite |
IOleInplaceSite |
Required as part of web browser control hosting interfaces. All methods return E_NOTIMPL . |
WBEventDispatch |
IDispatch |
This class is the sink for web browser control events. As the events arrive from the web browser control, it fires events to notify the client application. |
WBDocHostShowUI |
IDocHostShowUI |
I am only handling ::ShowMessage method which is responsible to intercept HTTP messages and notify the client via the Fire_ShowMessage event to determine what to do with the message. A typical message may look like this:
Your current security settings prohibit running ActiveX controls on this page. As a result, the page may not display correctly. |
WBOleCommandTarget |
IOleCommandTarget |
The purpose of this class is to intercept script errors via ::Exec method, and based on the m_lScriptError flag, either allow or disallow them. |
WBAuthenticate |
IAuthenticate |
This class intercepts requests for basic authentication from servers, and notifies the client using OnAuthentication event to obtain the username and password. Useful for clients wanting to automate the process of logging in using basic authentication schemes. |
WBDocHostUIHandler |
IDocHostUIHandler |
The purpose of this class is to intercept the context menu (::ShowContextMenu ), accelerator keys (::TranslateAccelerator ), and to set the UI flags (::GetHostInfo ). |
WBHttpSecurity |
IHttpSecurity |
This class intercepts HTTP related security problems, such as ERROR_HTTP_REDIRECT_ NEEDS_CONFIRMATION *, ERROR_INTERNET_SEC_ CERT_CN_INVALID * via the OnSecurityProblem method. Please note, using the ::OnSecurityProblem method or OnHTTPSecurityProblem event incorrectly can compromise the security of your application and potentially leave users of your application exposed to unwanted information disclosure. |
WBSecurityManager |
IInternetSecurityManager |
This class only implements the ::ProcessUrlAction method. It returns INET_E_DEFAULT_ACTION for the rest of the methods. Please note, using ::ProcessUrlAction method or SecurityManagerProcess UrlAction * event incorrectly may result in the incorrect processing of URL actions and possibly leave users susceptible to elevation of privilege attacks. |
WBServiceProvider |
IServiceProvider |
It is responsible to respond to the QueryService calls on our IUknown(IWB) in the ::QueryService method. |
WBWindowForBindingUI |
IWindowForBindingUI |
It returns a handle to a window via the ::GetWindow method which is used by MSHTML to display information in the client's user interface when necessary. Currently, this method returns a handle to the Internet Explorer server window. |
WBBSCBFileDL |
IBindStatusCallback
IHttpNegotiate |
An instance of this class is created and used to receive callbacks and notify the client app via OnFileDLxxxx events for all the file downloads, by the user clicking on a download link or by using ::DownloadUrlAsync method from the code. |
WBDownLoadManager |
IDownloadManager |
It implements ::Download method which in turn creates an instance of our IBindStatusCallback implementation (WBBSCBFileDL ), registers our BSCB for callbacks and notifies the client via OnFileDLxxxx events of the progress of the download. Each BSCB is given a unique ID and a pointer to it is stored in a simple array in the CvbWB class instance. This ID can be used by the client app to cancel a download by calling CancelFileDl passing the ID. |
CTmpBuffer |
|
A simple string buffer class, since CString is not available due to the minimum dependency requirement. |
CUrlParts |
|
A simple class which uses the InternetCrackUrl method of WinInet to break a given URL into its parts. This includes file name and extension, if available. |
WBPassThruSink |
CInternetProtocolSinkWithSP
IHttpNegotiate |
This class is the sink for the protocol handlers. |
WBDropTarget |
IDropTarget |
To handle custom dragdrop. |
WBDocHostUIHandler |
IDocHostUIHandler2 |
To handle GetOverrideKeyPath method. |
WBStream |
IStream |
To handle uploads with progress. |
* Ignore the space that has been added to avoid page scrolling.
Index
- Copy vbMHWB.dll located in the Binaries sub folder to your system directory.
- Register vbMHWB.dll using regsvr32.exe.
- Open VBDemo or MFCDemo project.
How to register, example:
Assuming the system dir path is 'C:\windows\system32\':
regsvr32.exe C:\windows\system32\vbMHWB.dll.
Both demo projects are almost identical in terms of GUI and their use of the control. The VBDemo was built using Visual Basic 6.0 and has no dependencies other than this control. The MFCDemo project was built using Visual C++ 6.0 and also has no other dependencies. With MFCDemo, you need to treat BOOL
as VARIANT_BOOL
(wizard translates VARIANT_BOOL
as BOOL
), and make sure that the value of an in/out BSTR
parameter is released (ClearBSTRPtr
method is provided as an example) before assigning a new value. This step is necessary to avoid memory leaks.
Build versions have been included in the Binaries subfolder for both projects.
Index
A list of new or modified properties, methods (90), and events (40) along with a log of changes has been included in vbMHWB.htm file.
Please see vbMHWB.htm for a list of changes.
- 15th March, 2006 - Current version (1.2.1.3).
- 13th May, 2005 - Initial version (1.0.0.1) posted.
Index