Chapter 13. Building In-Process Extensions with Browser Helper Objects

Most IE extensibility points are, in many ways, separated from the steps and transactions that take place behind the scenes of a page load or the display of the browser frame. Most extensions are used to control a piece of the browser's UI, whether it be a part of a page or a part of the browser window itself. Browser Helper objects (BHOs) fill in the missing piece of this story: they allow developers to access the events called and actions taken by IE silently. Much like a Windows service, these extensions sit in the background and perform tasks without having to interact with a user directly.

In this chapter, I discuss how you can build your own BHO and use this extension to tap into browser events and communicate with other processes. I begin the chapter with a quick introduction of these extensions and how they fit within the IE architecture. Next, I provide a demonstration of how to register, test, and uninstall an extension of this type. This chapter closes with examples of how you can use BHOs to tap into events exposed by the browser tab and process.

Understanding BHOs

BHOs are IE extensions that run in the browser process but have no formal UI. These extensions implement the IObjectWithSite interface. IE loads a new BHO instance for every tab loaded into the browser, and notifies a loaded extension of placement via the SetSite(...) method.

Despite not having an official UI requirement, BHOs can still interact with the UI thread of a tab process. Figure 13-1 shows a sample BHO that does just this, launching a message box during an event callback.

BHO launching a message box

Figure 13.1. BHO launching a message box

Like ActiveX controls, toolbars, search providers, and other IE extensions, users can manage BHOs. BHOs installed into IE can be enabled and disabled through the Manage Add-Ons dialog, as shown in Figure 13-2.

BHOs shown in the Manage Add-Ons dialog

Figure 13.2. BHOs shown in the Manage Add-Ons dialog

For each add-on, the dialog shows the name, publisher, enabled status, install date, and load time. This dialog does not allow users to uninstall a BHO, but it does allow the user to control whether the BHO is enabled and loaded. Load time, or time it takes for IE to load a BHO and run its SetSite() callback, was added as of IE 8 to encourage users to disable "slow" BHOs that might be negatively affecting their browser's performance.

Building a Generic BHO

BHOs are fairly simple to build, especially when built using the .NET Framework. As described in the preceding section, these objects are basic classes that implement the IObjectWithSite interface. The only other requirements aside from that are that this same class have a unique GUID, be registered among the machine's COM objects, and be listed under a particular registry key.

A BHO in C# (the language used for the remainder of this chapter) consists of a class with four basic sections: instance variables to hold a browser reference, an implementation of IObjectWithSite, functions that handle COM registration and unregistration, and invocation/definition of the IObjectWithSite API. Listing 13-1 demonstrates the first three requirements.

Example 13.1. Basic Architecture for a BHO

[
ComVisible(true), // Make the object visible to COM
Guid("48DEAC10-D583-4683-9345-3F8A117DE7A5"), // Create a new, unique GUID for each BHO
ClassInterface(ClassInterfaceType.None)] // Don't create an interface (allow only late-
                                         // bound access via IDispatch)
public class BhoSampleBasic : IObjectWithSite
{

    #region Variables ...

    #region IObjectWithSite Interface Implementation ...

    #region COM Registration Functions ...

}

In the example, a new BHO—BhoSampleBasic—is defined as a class that implements the IObjectWithSite interface. This class has a few important attributes. First, it makes marks this .NET class visible to COM. Next, it defines a GUID to uniquely identify the class. Last, it prevents .NET from exposing a class interface by setting the ClassInterface attribute to ClassInterface.None.

Omitting a public COM interface using ClassInterface.None is important! IE does not know how to talk to this extension natively—it clearly does not know every method and property for every BHO that was ever created or that will be. It does, however, recognize the object's base class: IObjectWithSite. Setting this value to ClassInterface.None forces callers (in this case, IE) to bypass the interface of the inherited class find a common base class. Basically, it forces IE and the BHO to communicate with each other using a "language" common to both.

The instance member of this class is a single variable, Browser. This variable is a WebBrowser type and is used to store a reference to the browser instance where the BHO is loaded. It is populated when the SetSite function is called as the BHO is loaded into IE (Listing 13-2).

Example 13.2. Instance Members of the Basic BHO Class

#region Instance Members

SHDocVw.WebBrowser Browser;

#endregion Variables

This library continues by defining the IObjectWithSite interface. This interface is implemented by the BHO class to handle the SetSite and GetSite calls made by the browser instance loading this extension. Listing 13-3 defines this API.

Example 13.3. The IObjectWithSite Interface

[ComVisible(true),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")] // Unlike the BHO's GUID, this GUID remains
                                              // the same for every BHO using it (since it
                                              // is indicating the GUID of IObjectWithSite,
                                              // a value constant on all Windows systems)
public interface IObjectWithSite
{
    [PreserveSig]
    int SetSite([MarshalAs(UnmanagedType.IUnknown)] object site);

    [PreserveSig]
    int GetSite(ref Guid guid, out IntPtr ppvSite);
}

The BHO implements this API within this class. Two functions are created: SetSite and GetSite. These functions (described in other chapters) receive and disseminate, respectively, the instance of the browser object that loaded an instance of this class (Listing 13-4).

Example 13.4. IObjectWithSite Implementation Used by a BHO Class

#region IObjectWithSite Interface Implementation

public int SetSite(object site)
{
    // If the site is valid, cast as a WebBrowser object and
    // store it in the Browser variable
    if (site != null)
    {
        // Set the site object to the Browser variable, casting it
        // to a WebBrowser object type
        Browser = (SHDocVw.WebBrowser)site;

        // Tell the user SetSite was called
        System.Windows.Forms.MessageBox.Show("SetSite called!");
    }
    else Browser = null;

    return S_OK;
}

public int GetSite(ref Guid guid, out IntPtr ppvSite)
{
    // Get the IUnknown for the Browser object
    IntPtr pUnk = Marshal.GetIUnknownForObject(Browser);

    // Request a pointer for the interface
    int hResult = Marshal.QueryInterface(pUnk, ref guid, out ppvSite);
// Release the object
    Marshal.Release(pUnk);

    // Return the result from the QI call
    return hResult;
}

#endregion IObjectWithSite Interface Implementation

The SetSite(...) function is called to pass the IUnknown pointer of the IE site to a class implementing the BandObject class. IE calls this whenever this class is loaded into its process. GetSite(...), in contrast, provides the IE site back to the caller of this function.

The sample here launches a MessageBox whenever this function is called. Since a BHO has no inherent UI, this MessageBox is used in the following section to demonstrate that an instance of this extension is in fact running.

Registering and Running BHOs

Once a BHO is implemented, it must be registered with both COM and IE in order for it to be loaded into the browser. Listing 13-5 shows the COM registration methods where this takes place. When a BHO library is registered or unregistered, the .NET registration process calls the Register(...) or Unregister() functions, respectively.

Example 13.5. COM Registration Methods for a BHO Class

#region COM Registration Methods

[ComRegisterFunction]
public static void Register(Type type)
{
    // Open (and create if needed) the main BHO key
    RegistryKey bhoKey =
        Registry.LocalMachine.CreateSubKey(
            @"SoftwareMicrosoftWindowsCurrentVersion" +
            @"ExplorerBrowser Helper Objects");

    // Open (and create if needed) the GUID key
    RegistryKey guidKey
        = bhoKey.CreateSubKey(
            type.GUID.ToString("B"));

    // Close the open keys
    bhoKey.Close();
    guidKey.Close();
}
...

BHOs must register themselves with IE in order for them to load inside the browser and be placed in each tab instance. Listing 13-6 shows an example registration for a BHO. Unlike with other extension types, BHOs may only be listed within the HKEY_LOCAL_MACHINE hive. Therefore, on Windows Vista and later, BHOs may only be registered and unregistered by an elevated process running with administrative permissions.

Within the HKEY_LOCAL_MACHINE hive, BHOs are listed as subkeys named with the GUID of the extension (Listing 13-6).

Example 13.6. Sample Registry Values for a BHO

HKEY_LOCAL_MACHINE
   Software
      Microsoft
         Windows
            CurrentVersion
               Explorer
                  Browser Helper Objects
                     {48DEAC10-D583-4683-9345-3F8A117DE7A5}

Because Windows Explorer also supports BHOs, you can prevent your BHO from loading into Windows Explorer by creating a REG_DWORD named NoExplorer within your BHO's subkey.

Once registered, IE will load an instance of this BHO whenever a tab is loaded. Figure 13-3 shows an example of this: the BHO class created in the previous section throws a MessageBox dialog whenever IE loads an instance of that class and calls the SetSite(...) function within it.

BHO showing a message box during a SetSite call

Figure 13.3. BHO showing a message box during a SetSite call

The Unregister() function is used to remove the registry entries created by the Register(...) function. Listing 13-7 shows the contents. When called, this removes the key associated with the extension's GUID.

Example 13.7. Unregistration Function for a BHO Class

[ComUnregisterFunction]
public static void Unregister(Type type)
{
    // Open up the main BHO key
    RegistryKey bhoKey =
        Registry.LocalMachine.OpenSubKey(
            @"SoftwareMicrosoftWindowsCurrentVersion" +
            @"ExplorerBrowser Helper Objects",
            true);

    // Delete the GUID key if it exists
    if (bhoKey != null) bhoKey.DeleteSubKey(
        type.GUID.ToString("B"), false);
}

#endregion COM Registration Methods

Once the registry key is removed, IE no longer loads the BHO nor lists it in the browser's list of add-ons in the Manage Add-Ons dialog.

Sinking Browser Events

Having an extension loaded into the browser process is quite useful. BHOs, however, are not limited to this. Given that this extension is passed a valid WebBrowser instance through SetSite, BHOs can take advantage of the wide range of functionality exposed by this object. Arguably, the most important piece of this function is the ability to sink (attach to) events on a WebBrowser object instance and those of its children. Using event interfaces such as DWebBrowserEvents2, developers can handle events triggered by the browser, the page, the user, or any other item interacting within the browser process.

The most common and useful events that a BHO can sink are exposed by the DWebBrowserEvents2 event sync interface. These events are raised by the browser object that hosts an instance of a BHO. Such events include BeforeNavigate2 (raised before a navigation), NavigationComplete2 (raised when a navigation is complete), and DocumentComplete (raised when a document has fully loaded).

Listing 13-8 shows an example of how a BHO could sink these events. The example in the last section is extended to call event handlers as events are raised. The SetSite() function, after grabbing the browser object instance passed to it, adds two event handlers.

Example 13.8. Web Browser Events Sunk During a SetSite Call

[SecurityPermission(SecurityAction.LinkDemand,
    Flags = SecurityPermissionFlag.UnmanagedCode),
ComVisible(true),
Guid("FE19878E-55B9-48AA-B293-2BDEEB2232DB"),
ClassInterface(ClassInterfaceType.None)]
public class BhoSampleEvents : NativeMethods.IObjectWithSite
{

    ...
    public int SetSite(object site)
    {
// If the site is valid, cast as a WebBrowser object and
        // store it in the Browser variable
        if (site != null)
        {
            // Set the site object, cast as a WebBrowser object,
            // to the Browser variable
            Browser = (SHDocVw.WebBrowser)site;

            // Add a handler for when navigation starts
            Browser.BeforeNavigate2 += Browser_BeforeNavigate2;

            // Add a handler for when the document has completely loaded
            Browser.DocumentComplete += Browser_DocumentComplete;
        }
        else
        {
            // Detatch the event handlers if they were attached
            Browser.BeforeNavigate2 -= Browser_BeforeNavigate2;
            Browser.DocumentComplete -= Browser_DocumentComplete;

            // Set the browser object to null
           Browser = null;
        }

        return S_OK;
    }

First, it ties the function Browser_BeforeNavigate2 as a callback to the BeforeNavigate2 event. Next, it ties the function Browser_DocumentComplete as a callback to the DocumentComplete event.

Listing 13-9 shows the callback function called when the BeforeNavigate2 event is raised. This function demonstrates its usage by displaying a MessageBox to the user before navigation within a specific tab takes place. This MessageBox shows the target url for the navigation.

Example 13.9. BHO Callback for the BeforeNavigate2 Event

void Browser_BeforeNavigate2(object pDisp, ref object URL, ref object Flags, ref object
    TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel)
{
    // If the site or url is null, do not continue
    if (pDisp == null || URL == null) return;

    // Convert the url object reference to a string
    string url = URL.ToString();

    // Show the url in a message box
    MessageBox.Show("Begin navigation to: " + url);
}

The example in Listing 13-10 performs a similar task in handling the DocumentComplete event using the callback Browser_DocumentComplete. Like the previous code snippet, this example also throws up a MessageBox to demonstrate its usage. This message box takes advantage of the browser's state: since the document target is complete, this document is now loaded into the browser object and accessible by the extension. Consequently, it displays the number of links contained within the loaded document by using functionality provided by the document object instance (using the IHTMLDocument2 interface).

Example 13.10. BHO Callback for the DocumentComplete Event

void Browser_DocumentComplete(object pDisp, ref object URL)
{
    // If the site or url is null, do not continue
    if (pDisp == null || URL == null) return;

    // Grab the document object off of the WebBrowser control
    IHTMLDocument2 document = (IHTMLDocument2)Browser.Document;
    if (document == null) return;

    // Report the total number of links on the current page
    MessageBox.Show("Total links on this page: " +
        document.links.length.ToString());
}

Figure 13-4 shows this callback in action. When MSN (www.msn.com) is loaded into the browser, the DocumentComplete event runs this callback in the browser's UI thread. The callback function (described in the previous listing) shows a message box displaying the number of links found in this page.

Message box showing the number of links on a page, called by a BHO

Figure 13.4. Message box showing the number of links on a page, called by a BHO

Developers may notice that the DocumentComplete event sunk by this example might trigger multiple times per page load. This specific event is raised once for each frame loaded on a page, followed last by this same event raised for the top-level window. Developers looking to distinguish between DocumentComplete events (or any other related events) raised for a frame and a top-level window can do so by comparing the pDisp pointer passed to the event handler to the pDisp pointer of the top-level window. One way to keep track of the pDisp pointer for such a comparison is to save a reference to the same pDisp pointer passed to SetSite (in that case, the variable is site). During DocumentComplete, if the pDisp pointer does not reference the same object as the version passed during SetSite, one can assume that the event was raised for something other than the top-level window.

Those developing plug-ins should also take note of the fact that callbacks are run in the IE UI thread (as noted previously). This means that IE takes away user control (as well as the ability to update or change its UI) while a callback function is running. Controls attaching to these events should spend as little time as possible in these functions or, at the very least, spawn another thread to handle any reaction to these events. Slow or clumsy extensions can ruin a user's browser experience—a BHO that spends, for instance, 5 seconds processing data on every DocumentComplete event will introduce a 5-second hang in the browser UI. Users might not appreciate that.

Summary

In this chapter, I talked about how you can build your own IE extension that runs in-process and out of sight, allowing you to access the events and functionality of IE without the requirement or overhead of a UI. I started this chapter by introducing the concept of BHOs, their goals, and how they fit into the overall IE architecture. Following this was an example of how to implement a BHO using C# and the .NET Framework. I then talked about how these extensions are registered, loaded, managed, and removed from the browser. I ended the chapter by expanding on this example by accessing the owning browser object and tying into events exposed from it.

BHOs offer developers a simple way to tap into the browser and enhance the functionality of pages. You can build a basic extension in a few simple steps, and tie into browser events in a simple way too. This extension is one of the simplest forms of binary IE extensions; while they cannot be constructed using simple XML or registry entries, their structure and implementation is straightforward and fairly lightweight. The functionality of BHOs is clearly not limited to the examples shown in this chapter; since these extensions have access to the browser tab and the browser object, these extensions have the same access and power as other binary IE extensions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.80.45