Fun With XBL and XPConnect

Introduction

This article describes an application of XBL in which a simple XPCOM interface is made accessible to a XUL widget. The interface definition feature of XBL is used to define an interface through XPConnect to a C++ object that does auto completion. Once the regular XUL textfield widget is bound to this interface, it calls the auto complete function of the object using regular JavaScript. The basic model of interaction is as follows:

Image:xpconnect_textfield.png

Binding to the XPCOM Object

The widget holds onto an XPCOM object that is the auto complete engine that will perform our auto complete lookups.

<binding name="autocomplete" extends="xul:box">
  <content>
    <xul:textfield class="addressingWidget"/>
    <xul:menupopup/>
  </content>

  <implementation>
    <property name="autoCompleteSession">
      <![CDATA[
       Components.classes['component://netscape/messenger/autocomplete&type=addrbook'].
        getService(Components.interfaces.nsIAutoCompleteSession);
      ]]>
    </property>

So we've defined a property on the widget called autoCompleteSession. The initial value of this property evaluates to an xp-connect object. Now accesses to autoCompleteSession will return the xp-connect object.

Exposing the XPCOM Interfaces

One somewhat nasty trick you need to do is to manually expose the interfaces of the XPCOM object that you want your widget to support. All you have to do is specifically defining a method on the XBL widget which forwards the method call to the XPCOM object.

    <method name="autoComplete">
      <argument name="aSearchString"/>
      <argument name="resultListener"/>
      <body>
        <![CDATA[
          return this.autoCompleteSession.autoComplete(null,
            anonymousContent[0], aSearchString, this.autoCompleteListener);
        ]]>
      </body>
    </method>

You can see that the body of the method is just getting the auto complete session object and calling the auto complete method on it.

Implementing a Widget Interface

The next thing I needed to do was to implement an interface on the widget which I could then pass into the auto complete session. I wanted the auto complete session to call back into the widget with search results. I could apply a trick similar to what I did for the XPCOM object:

    <property name="autoCompleteListener">
      <![CDATA[
        ({
          onAutoCompleteResult: function(aItem, aOriginalString, aMatch)
          {
            if ( aItem )
            {
              anonymousContent[0].value = aMatch;
            }
          }
        })
      ]]>
    </property>

As long as the JS for the value of autoCompleteListener evaluates to an object (and wrapping the expression with a set of parens like I did, does this), then the value of autoCompleteListener is an object that implements my interface.

Now I can pass the result of autoCompleteListener into methods that require an auto complete listener (like my auto complete session object).

Creating the Event Handler

The last part is the easy part. I wanted a handler that would kick off the auto complete search. The handler calls the auto complete method we've exposed on the widget which in turn forwards the call to the XPCOM object, passing in our implementation of nsIAutoCompleteListener.

    <handlers>
      <handler type="keypress" keycode="vk_return"
        value="autoComplete(anonymousContent[0].value, this.autoCompleteListener);"/>
    </handlers>
  </implementation>
</binding>

Original Document Information