wrappedJSObject

wrappedJSObject is a property sometimes available on XPConnect wrappers. When available, it lets you access the JavaScript object hidden by the wrapper.

There are two kinds of XPConnect wrappers that support the wrappedJSObject property:

  • XPCNativeWrappers which are used to protect the chrome code working with content objects. See XPCNativeWrapper for detailed documentation.
  • Regular XPConnect wrappers which you can encounter, for example, when using XPCOM components implemented in JS.

This article focuses on the latter kind of wrappers, which hide any properties or methods on the component that are not part of the supported interfaces as declared in xpidl.

The rest of this article demonstrates what XPConnect wrappers do and how wrappedJSObject can be used to bypass them.

Example component

To see how the wrappedJSObject property works, we need an example XPCOM component implemented in JS. See How to Build an XPCOM Component in Javascript for details on creating one.

For simplicity we omit the component registration code. Suppose we register the component below with the @myself.com/my-component;1 contract.

// constructor
function HelloWorld() {
};

HelloWorld.prototype = {
  hello: function() {
    return "Hello World!";
  },

  QueryInterface: function(aIID)
  {
    if (!aIID.equals(Components.interfaces.nsISupports) &&
        !aIID.equals(Components.interfaces.nsIHelloWorld))
      throw Components.results.NS_ERROR_NO_INTERFACE;
    return this;
  }
};

XPConnect wrapping

Now let's get a reference to our component. In this example we use getService, but as long as we get the reference from XPCOM, our component gets wrapped by XPConnect in the same way:

var comp = Components.classes["@myself.com/my-component;1"].getService();

If we try to call the hello() method we defined in our component implementation, we get:

> comp.hello();
TypeError on line 1: comp.hello is not a function

This happens because, as we mentioned earlier, comp is not the HelloWorld JS object itself, but an XPConnect wrapper around it:

> dump(comp);
[xpconnect wrapped nsISupports]

The idea of these wrappers is to make the JavaScript-implemented XPCOM components look just like any other XPCOM component to the user. This also makes the public interface of the component clearer and provides protection for the component's internal data.

Calling QueryInterface on the wrapper works, because it is defined in the nsISupports interface, and the wrapper knows the underlying object implements nsISupports:

> comp.QueryInterface(Components.interfaces.nsIHelloWorld);
[xpconnect wrapped (nsISupports, nsIHelloWorld)]

As you can see, the QueryInterface call also made the wrapper know about the other interface our component supports. Assuming the nsIHelloWorld interface defines the hello method, we can now call it:

> comp.hello()
Hello World!

While this behavior is nice for production code as it forces you to clearly define the interfaces that should be used to access the component, it's inconvenient to write the interfaces (and recompile each time you change them) when working on a prototype of the component.

Meet wrappedJSObject

XPConnect lets you bypass its wrappers and access the underlying JS object directly using the wrapper.wrappedJSObject property if the wrapped object allows this.

More specifically, as XPConnect source comments say, you can get comp.wrappedJSObject if three conditions are met:

  • comp really is an XPConnect wrapper around a JS object. Wrappers around non-JS objects don't have this property.
  • The underlying object has a wrappedJSObject property that returns a JS object.
  • nsIXPCSecurityManager allows access (see the source code comments for details; this is usually not an issue for Mozilla extensions and applications)

This means that in order to access the JS object implementing our component directly, we must modify the component. For example:

function HelloWorld() {
  this.wrappedJSObject = this;
};

Now we can get the component directly:

var comp = Components.classes["@myself.com/my-component;1"]
                     .getService().wrappedJSObject;

This is a real JS object:

> comp
[object Object]

So we can access any property on it:

> comp.hello();
Hello World!

This functionality can be used for quick prototyping, as well as to painlessly pass arbitrary JS values to the component (which can be used for sharing complex JS data in particular).