WebBrowser Customization (Part 2)

Part 1 of this tutorial introduced several of the standard hosting interfaces for the WebBrowser Control: IDocHostUIHandler, IDocHostShowUI, and ICustomDoc, to name a few. Part 2 of this article explains how to retrieve a reference to the client site while processing IPersistMoniker::Load, and how to use the IServiceProvider and IOleCommandTarget interfaces to send messages between the Active document (DocObject) and its host.

  • IPersistMoniker and IBindCtx
  • IServiceProvider
    • QueryService Example
    • CLSID_CMarkup
  • IOleCommandTarget
    • Showing a Certificate Dialog
    • Controlling Navigation (Revisited)
    • Specifying the Zone Icon
    • More Examples of Commands
  • Context Menus and Extensions
    • Appending Extensions to Your Custom Menu
    • Enabling and Disabling Menu Items
  • References

IPersistMoniker and IBindCtx

When the WebBrowser Control determines that a hyperlinked resource is associated with a helper application, it creates an instance of the target application and prompts the Active document (DocObject) to load the resource by using the IPersistMoniker interface. Before the browser navigates to a document, it registers a reference to its IOleClientSite interface in the binding context by calling IBindCtx::RegisterObjectParam. This reference allows the DocObject to retrieve and use the client site during the call to IPersistMoniker::Load.

The following example demonstrates how to retrieve and set the client site from the binding context:

HRESULT CDoc::Load(BOOL fFullyAvailable, IMoniker *pimkName, LPBC pibc, DWORD grfMode)
{
    HRESULT hr = S_OK;
    CComPtr<IUnknown> spUnk;
    CComPtr<IBindCtx> spBindCtx;

    // SHDOCVW puts the client site in the bind context.
    if (pibc)
    {
        pibc->GetObjectParam(SZ_HTML_CLIENTSITE_OBJECTPARAM, &spUnk);
        if (spUnk)
        {
            CComQIPtr<IOleClientSite> spSite(spUnk);
            
            if (spSite)
            {
                hr = SetClientSite(spSite);
            }
        }
    }
}

IServiceProvider

The IServiceProvider interface is a generic access mechanism to locate a GUID-identified service that is provided through a control or any other object. Using IServiceProvider::QueryService, the caller specifies a service identifier (SID), the IID of the desired interface, and the address of the interface pointer variable that is set upon a successful return.

In many ways, IServiceProvider::QueryService functions like QueryInterface. However, the more adaptable IServiceProvider mechanism enables an implementer to delegate the query to one of its member objects, or to hand off the request to a callback hierarchy that searches for an object to support the requested IID. In this way, the implementer is not required to recognize the requested interfaces. In common usage, IServiceProvider::QueryService is used to improve the discoverability of some interfaces.

QueryService Example

For example, a call to QueryService with SID_SShellBrowser retrieves the nearest shell browser, such as the Windows Internet Explorer WebBrowser Control. If the DocObject is hosted in the search pane, this technique will prevent it from mistakenly executing commands on the main browser window. SID_SShellBrowser service is specific to the WebBrowser control; any DocObject that implements the IBrowserService interface should respond to queries for this service.

The following example demonstrates this basic scenario:

    CComPtr<IServiceProvider> pSvc; 
    CComPtr<IShellBrowser>    pShb;
    HRESULT                   hr;
    
    hr = m_pUnk->QueryInterface(IID_IServiceProvider, &pSvc);
    if (S_OK == hr && pSvc)
    {
        hr = pSvc->QueryService(SID_SShellBrowser, IID_IShellBrowser, &pShb);
        if (E_NOINTERFACE == hr)
            return;   // pSvc released automatically.

        // . . . Use the interface here.
    }

CLSID_CMarkup

The special CLSID_CMarkup GUID is used to determine whether an object is a native MSHTML markup object. No code except Internet Explorer should use this CLSID. Your QueryService or QueryInterface implementation should return E_NOINTERFACE if invoked with this GUID.

DEFINE_GUID(CLSID_CMarkup, 0x3050F4FB, 0x98B5, 0x11CF, 0xBB, 0x82, 0, 0xAA, 0, 0xBD, 0xCE, 0x0B);

Note   The MSHTML implementation uses a non-standard QueryInterface for CLSID_CMarkup to obtain an object pointer without adding a reference. In other words, the out parameter ppv does not receive an interface pointer; it contains the address of the object behind pUnknown.

 

IOleCommandTarget

The IOleCommandTarget interface enables objects and their containers to dispatch commands to each other. Available commands are defined by integer identifiers from a command group, which is itself identified by command group ID (also a GUID). The interface enables a caller to query for one or more supported commands within a group and to issue a command to the object.

The WebBrowser Control uses the IOleCommandTarget interface extensively. The following sections highlight just a few of the ways that the client site can communicate with its control.

Showing a Certificate Dialog

With Microsoft Internet Explorer 6 for Windows XP Service Pack 2 (SP2) and later, you can show the Certificate dialog box when the user is viewing a valid Secure Hypertext Transfer Protocol (HTTPS) site. This has the same result as the user clicking the lock icon in Internet Explorer. You can use the DWebBrowserEvents2::SetSecureLockIcon event to show your own icon.

#define SHDVID_SSLSTATUS 33

IOleCommandTarget *pct;
if (SUCCEEDED(pWebBrowser2->QueryInterface(IID_IOleCommandTarget, (void **)&pct)))
{
   pct->Exec(&CGID_ShellDocView, SHDVID_SSLSTATUS, 0, NULL, NULL);
   pct->Release();
}

Controlling Navigation (Revisited)

If the MSHTML control is aggregated, the controlling DocObject is in a position to regulate navigation events. The fact that a document can navigate on its own implies that it will also take care of updating the navigation history.

In Internet Explorer 6 and later, the DocObject can indicate to the client site that it can navigate using CGID_DocHostCmdPriv (a privately defined command group GUID) and the DOCHOST_DOCCANNAVIGATE command. A pointer to the object that implements the IHTMLWindow2 interface is passed with the command in the VARIANTARG* parameter pvaIn. (Set pvaIn to NULL if the document cannot perform its own navigation.)

DEFINE_GUID(CGID_DocHostCmdPriv, 0x000214D4L, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
#define DOCHOST_DOCCANNAVIGATE  0

// Create variant, and initialize with a pointer to our IUnknown.
VARIANTARG var;

V_VT(&var)      = VT_UNKNOWN;
V_UNKNOWN(&var) = (IUnknown*)this;

// Execute IOleCommandTarget with command group and ID.
m_pCmdTarg->Exec(&CGID_DocHostCmdPriv, DOCHOST_DOCCANNAVIGATE, 0L, &var, NULL);

Specifying the Zone Icon

In conjunction with CGID_Explorer, the SBCMDID_MIXEDZONE command is used to determine the zone icon and text displayed in the Internet Explorer status bar.

Active document objects hosted in Internet Explorer 6 for Windows XP SP2 or later can respond to the SBCMDID_MIXEDZONE command to determine the zone icon and text displayed in the Internet Explorer status bar. The document object should implement IOleCommandTarget, and Internet Explorer calls IOleCommandTarget::Exec with the CGID_Explorer command group.

To specify the zone icon:

  1. Define SBCMDID_MIXEDZONE.

    #define SBCMDID_MIXEDZONE 39
    
  2. Initialize pvaOut with VariantInit.

  3. Determine the zone you want to specify and then set pvaOut as follows. To specify:

    • Internet, Intranet, Trusted or Untrusted zones: set pvaOut->vt to VT_UI4, and set pvaOut->ulVal to URLZONE_INTERNET, URLZONE_INTRANET, URLZONE_TRUSTED or URLZONE_UNTRUSTED respectively.
    • A mixed zone: set pvaOut->vt to VT_NULL.
    • An unknown zone: set pvaOut->vt to VT_EMPTY.

If an active document does not handle SBCMDID_MIXEDZONE, the default behavior will show Unknown Zone in the status bar.

Security Warning: If you cannot reliably determine your document's zone, you should not handle this command.

More Examples of Commands

The IDM_CONTEXT command (CGID_EditStateCommands command group) is used to determine whether editing commands should be routed to the host first. An IOleCommandTarget implementation returns S_OK to indicate that editing commands should be routed to the host first.

If the host is designed to handle editing commands, it will respond to the SID_SEditCommandTarget service identifier by providing an IOleCommandTarget interface to process editing commands. The editing commands are defined in the CGID_MSHTML command group, as shown in the following code.

DEFINE_GUID(CGID_MSHTML, 0xde4ba900, 0x59ca, 0x11cf, 0x95, 0x92, 0x44, 0x45, 0x53, 0x54, 0, 0);

For more information on editing commands, see Introduction to MSHTML Editing and MSHTML Command Identifiers reference.

Context Menus and Extensions

The IDocHostUIHandler::ShowContextMenu method was introduced in part one of this article. The IDocHostUIHandler interface is designed to be overridden by applications that host the WebBrowser control. It is not intended for use by Browser Helper Objects (BHOs) because, in addition to the problem of appending items to the standard menu discussed below, only one add-on at a time can override IDocHostUIHandler and multiple add-ons can easily conflict with each other.

Important    In Windows Internet Explorer 7, the technique for overriding the context menu from a DocObject host is the same as Internet Explorer 6; however, the host must implement its own menu resources. The internal resources of Internet Explorer should not be used as they may change or move (as has been done in Internet Explorer 7). See Appending Extensions to Your Custom Menu below.

 

The following examples demonstrate how to use the IOleCommandTarget interface with SHDVID_GETMIMECSETMENU and SHDVID_ADDMENUEXTENSIONS (CGID_ShellDocView command group) to add and remove custom menu options from the standard Internet Explorer context menu:

#define SHDVID_GETMIMECSETMENU   27
#define SHDVID_ADDMENUEXTENSIONS 53 

In Internet Explorer 6 and earlier, the WebBrowser Control gets its context menu resources from Shdoclc.dll. The following code loads the WebBrowser Control shortcut menu resource from Shdoclc.dll, chooses the correct menu for the context, loads language and extension resources from the registry, removes the menu item corresponding to the IDM_VIEWSOURCE command, and then displays the pop-up menu.

HRESULT CBrowserHost::ShowContextMenu(DWORD dwID,
                                     POINT *ppt,
                                     IUnknown *pcmdTarget,
                                     IDispatch *pdispObject) 
{
   #define IDR_BROWSE_CONTEXT_MENU  24641
   #define SHDVID_GETMIMECSETMENU   27
   #define SHDVID_ADDMENUEXTENSIONS 53

   HRESULT hr;
   HINSTANCE hinstSHDOCLC;
   HWND hwnd;
   HMENU hMenu;
   CComPtr<IOleCommandTarget> spCT;
   CComPtr<IOleWindow> spWnd;
   MENUITEMINFO mii = {0};
   CComVariant var, var1, var2;

   hr = pcmdTarget->QueryInterface(IID_IOleCommandTarget, (void**)&spCT);
   hr = pcmdTarget->QueryInterface(IID_IOleWindow, (void**)&spWnd);
   hr = spWnd->GetWindow(&hwnd);

   hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
   
   if (hinstSHDOCLC == NULL)
   {
       // Error loading module -- fail as securely as possible.
       return;
   }

   hMenu = LoadMenu(hinstSHDOCLC,
                    MAKEINTRESOURCE(IDR_BROWSE_CONTEXT_MENU));

   hMenu = GetSubMenu(hMenu, dwID);

   // Get the language submenu.
   hr = spCT->Exec(&CGID_ShellDocView, SHDVID_GETMIMECSETMENU, 0, NULL, &var);

   mii.cbSize = sizeof(mii);
   mii.fMask  = MIIM_SUBMENU;
   mii.hSubMenu = (HMENU) var.byref;

   // Add language submenu to Encoding context item.
   SetMenuItemInfo(hMenu, IDM_LANGUAGE, FALSE, &mii);

   // Insert Shortcut Menu Extensions from registry.
   V_VT(&var1) = VT_INT_PTR;
   V_BYREF(&var1) = hMenu;

   V_VT(&var2) = VT_I4;
   V_I4(&var2) = dwID;

   hr = spCT->Exec(&CGID_ShellDocView, SHDVID_ADDMENUEXTENSIONS, 0, &var1, &var2);

   // Remove View Source.
   DeleteMenu(hMenu, IDM_VIEWSOURCE, MF_BYCOMMAND);

   // Show shortcut menu.
   int iSelection = ::TrackPopupMenu(hMenu,
                                     TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
                                     ppt->x,
                                     ppt->y,
                                     0,
                                     hwnd,
                                     (RECT*)NULL);

   // Send selected shortcut menu item command to shell.
   LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);

   FreeLibrary(hinstSHDOCLC);
   return S_OK;
}

Security Warning: Using LoadLibrary incorrectly can compromise the security of your application by loading the wrong DLL. Refer to the LoadLibrary documentation for information on how to correctly load DLLs with different versions of Windows.

Appending Extensions to Your Custom Menu

In the previous example, the menu (hMenu) and menu context identifier (dwID) are passed as arguments to the IOleCommandTarget with the SHDVID_ADDMENUEXTENSIONS command. This operation inserts the registered menu extensions into the menu.

If you choose to replace the standard menu with your own, you can still append menu extensions to your custom menu. Simply include a blank IDM_MENUEXT_PLACEHOLDER menu option in your menu definition to indicate where the custom commands are to be inserted. Then pass your own menu as the command target. Menu extensions will be inserted just before the placeholder.

You can also add your own custom command to the standard menu by inserting the menu option before IDM_MENUEXT_PLACEHOLDER, as shown in the following example.

#define IDM_MENUEXT_PLACEHOLDER  6047

// If the placeholder is gone or was never there, then just exit.
if (GetMenuState(hMenu, IDM_MENUEXT_PLACEHOLDER, MF_BYCOMMAND) != (UINT) -1)
{
   InsertMenu(hMenu,                    // The Context Menu
       IDM_MENUEXT_PLACEHOLDER,         // The item to insert before
       MF_BYCOMMAND|MF_STRING,          // Insert by item ID and str value
       IDM_MENUEXT_FIRST__ + nExtCur,   // The command ID
       (LPTSTR)aOpts[nExtCur].pstrText);// Some menu command text
    
   // Remove placeholder.
   DeleteMenu(hMenu, IDM_MENUEXT_PLACEHOLDER, MF_BYCOMMAND);
}

The menu IDs for extensions fall between IDM_MENUEXT_FIRST__ and IDM_MENUEXT_LAST__ for a maximum of 32 custom commands.

Enabling and Disabling Menu Items

The menus in Internet Explorer use resource identifiers from mshtmcid.h to specify which command is executed when the menu item is clicked. Using the same resource IDs, you can easily determine whether the command has been enabled or disabled by calling IOleCommandTarget::QueryStatus, as shown in the following code snippet.

for (i = 0; i < GetMenuItemCount(hMenu); i++)
{
    OLECMD olecmd.cmdID = GetMenuItemID(hMenu, i);
    if (olecmd.cmdID > 0)
    {
        UINT mf;
        spCmdTarget->QueryStatus(&CGID_MSHTML, 1, &olecmd, NULL);
        switch (olecmd.cmdf)
        {
        case OLECMDSTATE_UP:
        case OLECMDSTATE_NINCHED:
            mf = MF_BYCOMMAND | MF_ENABLED | MF_UNCHECKED;
            break;

        case OLECMDSTATE_DOWN:
            mf = MF_BYCOMMAND | MF_ENABLED | MF_CHECKED;
            break;

        case OLECMDSTATE_DISABLED:
        default:
            mf = MF_BYCOMMAND | MF_DISABLED | MF_GRAYED;
            break;
        }
        CheckMenuItem(hMenu, olecmd.cmdID, mf);
        EnableMenuItem(hMenu, olecmd.cmdID, mf);
    }
}

References

The following table indicates where the identifiers described in this article are defined:

Identifier Header File 
CGID_EditStateCommands Mshtml.h
CGID_Explorer Shlguid.h
CGID_MSHTML Mshtmhst.h
CGID_ShellDocView Shlguid.h
IDM_CONTEXT Mshtmcid.h
SID_SEditCommandTarget Mshtml.h
SID_SShellBrowser Shlguid.h
SZ_HTML_CLIENTSITE_OBJECTPARAM  Mshtmhst.h